@@ -10,7 +10,7 @@ use pulldown_cmark::{html, CodeBlockKind, CowStr, Event, Options, Parser, Tag};
10
10
11
11
use std:: borrow:: Cow ;
12
12
use std:: fmt:: Write ;
13
- use std:: path:: Path ;
13
+ use std:: path:: { Component , Path , PathBuf } ;
14
14
15
15
pub use self :: string:: {
16
16
take_anchored_lines, take_lines, take_rustdoc_include_anchored_lines,
@@ -63,19 +63,55 @@ pub fn id_from_content(content: &str) -> String {
63
63
normalize_id ( trimmed)
64
64
}
65
65
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
+
66
101
/// Fix links to the correct location.
67
102
///
68
103
/// This adjusts links, such as turning `.md` extensions to `.html`.
69
104
///
70
105
/// `path` is the path to the page being rendered relative to the root of the
71
106
/// 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.
75
109
fn adjust_links < ' a > ( event : Event < ' a > , path : Option < & Path > ) -> Event < ' a > {
76
110
lazy_static ! {
77
111
static ref SCHEME_LINK : Regex = Regex :: new( r"^[a-z][a-z0-9+.-]*:" ) . unwrap( ) ;
78
112
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( ) ;
79
115
}
80
116
81
117
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> {
84
120
if let Some ( path) = path {
85
121
let mut base = path. display ( ) . to_string ( ) ;
86
122
if base. ends_with ( ".md" ) {
87
- base. replace_range ( base. len ( ) - 3 .., ".html " ) ;
123
+ base. replace_range ( base. len ( ) - 3 .., "" ) ;
88
124
}
89
- return format ! ( "{}{}" , base, dest) . into ( ) ;
125
+ return format ! ( "# {}{}" , normalize_path_id ( base) , dest. replace ( "#" , "-" ) ) . into ( ) ;
90
126
} else {
91
127
return dest;
92
128
}
@@ -104,18 +140,34 @@ fn adjust_links<'a>(event: Event<'a>, path: Option<&Path>) -> Event<'a> {
104
140
if !base. is_empty ( ) {
105
141
write ! ( fixed_link, "{}/" , base) . unwrap ( ) ;
106
142
}
107
- }
108
143
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) ;
115
158
} 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
+ }
119
171
}
120
172
dest
121
173
}
0 commit comments