@@ -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:: { Path , PathBuf , Component } ;
14
14
15
15
pub use self :: string:: {
16
16
take_anchored_lines, take_lines, take_rustdoc_include_anchored_lines,
@@ -63,19 +63,52 @@ 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 ( )
78
+ . to_str ( )
79
+ . map_or ( false , |s| s. ends_with ( '/' ) ) ;
80
+ let mut normalized = PathBuf :: new ( ) ;
81
+ for component in path. as_ref ( ) . components ( ) {
82
+ match & component {
83
+ Component :: ParentDir => {
84
+ if !normalized. pop ( ) {
85
+ normalized. push ( component) ;
86
+ }
87
+ }
88
+ _ => {
89
+ normalized. push ( component) ;
90
+ }
91
+ }
92
+ }
93
+ if ends_with_slash {
94
+ normalized. push ( "" ) ;
95
+ }
96
+ normalized. to_str ( ) . unwrap ( ) . replace ( "\\ " , "-" ) . replace ( "/" , "-" )
97
+ }
98
+
66
99
/// Fix links to the correct location.
67
100
///
68
101
/// This adjusts links, such as turning `.md` extensions to `.html`.
69
102
///
70
103
/// `path` is the path to the page being rendered relative to the root of the
71
104
/// 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.
105
+ /// page go to the anchors that has a path id prefix. Normal page rendering
106
+ /// sets `path` to None.
75
107
fn adjust_links < ' a > ( event : Event < ' a > , path : Option < & Path > ) -> Event < ' a > {
76
108
lazy_static ! {
77
109
static ref SCHEME_LINK : Regex = Regex :: new( r"^[a-z][a-z0-9+.-]*:" ) . unwrap( ) ;
78
110
static ref MD_LINK : Regex = Regex :: new( r"(?P<link>.*)\.md(?P<anchor>#.*)?" ) . unwrap( ) ;
111
+ static ref HTML_MD_LINK : Regex = Regex :: new( r"(?P<link>.*)\.(html|md)(?P<anchor>#.*)?" ) . unwrap( ) ;
79
112
}
80
113
81
114
fn fix < ' a > ( dest : CowStr < ' a > , path : Option < & Path > ) -> CowStr < ' a > {
@@ -84,9 +117,9 @@ fn adjust_links<'a>(event: Event<'a>, path: Option<&Path>) -> Event<'a> {
84
117
if let Some ( path) = path {
85
118
let mut base = path. display ( ) . to_string ( ) ;
86
119
if base. ends_with ( ".md" ) {
87
- base. replace_range ( base. len ( ) - 3 .., ".html " ) ;
120
+ base. replace_range ( base. len ( ) - 3 .., "" ) ;
88
121
}
89
- return format ! ( "{}{}" , base, dest) . into ( ) ;
122
+ return format ! ( "# {}{}" , normalize_path_id ( base) , dest. replace ( "#" , "-" ) ) . into ( ) ;
90
123
} else {
91
124
return dest;
92
125
}
@@ -104,18 +137,34 @@ fn adjust_links<'a>(event: Event<'a>, path: Option<&Path>) -> Event<'a> {
104
137
if !base. is_empty ( ) {
105
138
write ! ( fixed_link, "{}/" , base) . unwrap ( ) ;
106
139
}
107
- }
108
140
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
- }
141
+ // In `print.html`, print page links would all link to anchors on the print page.
142
+ if let Some ( caps) = HTML_MD_LINK . captures ( & dest) {
143
+ fixed_link. push_str ( & caps[ "link" ] ) ;
144
+ if let Some ( anchor) = caps. name ( "anchor" ) {
145
+ fixed_link. push_str ( anchor. as_str ( ) ) ;
146
+ }
147
+ } else {
148
+ fixed_link. push_str ( & dest) ;
149
+ } ;
150
+
151
+ let mut fixed_anchor_for_print = String :: new ( ) ;
152
+ fixed_anchor_for_print. push_str ( "#" ) ;
153
+ fixed_anchor_for_print. push_str ( & normalize_path_id ( & fixed_link) . replace ( "#" , "-" ) ) ;
154
+ return CowStr :: from ( fixed_anchor_for_print) ;
115
155
} else {
116
- fixed_link. push_str ( & dest) ;
117
- } ;
118
- return CowStr :: from ( fixed_link) ;
156
+ // In normal page rendering, links to anchors on another page.
157
+ if let Some ( caps) = MD_LINK . captures ( & dest) {
158
+ fixed_link. push_str ( & caps[ "link" ] ) ;
159
+ fixed_link. push_str ( ".html" ) ;
160
+ if let Some ( anchor) = caps. name ( "anchor" ) {
161
+ fixed_link. push_str ( anchor. as_str ( ) ) ;
162
+ }
163
+ } else {
164
+ fixed_link. push_str ( & dest) ;
165
+ } ;
166
+ return CowStr :: from ( fixed_link) ;
167
+ }
119
168
}
120
169
dest
121
170
}
0 commit comments