Skip to content

Commit 495c9e5

Browse files
committed
sorting templates, base rustdoc tests/rustdoc/ pass
1 parent 7db0a69 commit 495c9e5

File tree

2 files changed

+95
-65
lines changed

2 files changed

+95
-65
lines changed

src/librustdoc/html/render/offset_template.rs

Lines changed: 89 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,25 @@
11
use std::fmt;
22
use std::marker::PhantomData;
3+
use rustc_data_structures::fx::FxHashSet;
4+
use std::str::FromStr;
35

46
use serde::{Serialize, Deserialize};
57

6-
/// Append-only templates for lists of items.
8+
/// Append-only templates for sorted lists of items.
79
///
810
/// Last line of the rendered output is a comment encoding the next insertion point.
911
#[derive(Debug, Clone)]
1012
pub(crate) struct OffsetTemplate<F> {
1113
format: PhantomData<F>,
12-
contents: String,
13-
offset: Offset,
14+
before: String,
15+
after: String,
16+
contents: FxHashSet<String>,
1417
}
1518

1619
#[derive(Serialize, Deserialize, Debug, Clone)]
1720
struct Offset {
18-
next_insert: usize,
19-
empty: bool,
21+
start: usize,
22+
delta: Vec<usize>,
2023
}
2124

2225
impl<F> OffsetTemplate<F> {
@@ -34,56 +37,70 @@ impl<F> OffsetTemplate<F> {
3437
}
3538

3639
/// Template will insert contents between `before` and `after`
37-
pub(crate) fn before_after(before: &str, after: &str) -> Self {
38-
let contents = format!("{before}{after}");
39-
let offset = Offset { next_insert: before.len(), empty: true };
40-
Self { format: PhantomData, contents, offset }
40+
pub(crate) fn before_after<S: ToString, T: ToString>(before: S, after: T) -> Self {
41+
let before = before.to_string();
42+
let after = after.to_string();
43+
OffsetTemplate { format: PhantomData, before, after, contents: Default::default() }
4144
}
4245
}
4346

4447
impl<F: FileFormat> OffsetTemplate<F> {
4548
/// Puts the text `insert` at the template's insertion point
46-
pub(crate) fn append(&mut self, insert: &str) -> Result<(), Error> {
47-
if !self.contents.is_char_boundary(self.offset.next_insert) {
48-
return Err(Error);
49-
}
50-
let sep = if self.offset.empty { "" } else { F::SEPARATOR };
51-
self.offset.empty = false;
52-
let after = self.contents.split_off(self.offset.next_insert);
53-
self.contents.push_str(sep);
54-
self.contents.push_str(insert);
55-
self.contents.push_str(&after);
56-
self.offset.next_insert += sep.len() + insert.len();
57-
Ok(())
49+
pub(crate) fn append(&mut self, insert: String) {
50+
self.contents.insert(insert);
5851
}
5952
}
6053

6154
impl<F: FileFormat> fmt::Display for OffsetTemplate<F> {
6255
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63-
let offset = serde_json::to_string(&self.offset).map_err(|_| fmt::Error)?;
64-
write!(f, "{}\n{}{}{}", self.contents, F::COMMENT_START, &offset, F::COMMENT_END)
56+
let mut delta = Vec::default();
57+
write!(f, "{}", self.before)?;
58+
let mut contents: Vec<_> = self.contents.iter().collect();
59+
contents.sort_unstable();
60+
let mut sep = "";
61+
for content in contents {
62+
delta.push(sep.len() + content.len());
63+
write!(f, "{}{}", sep, content)?;
64+
sep = F::SEPARATOR;
65+
}
66+
let offset = Offset { start: self.before.len(), delta };
67+
let offset = serde_json::to_string(&offset).unwrap();
68+
write!(f, "{}\n{}{}{}", self.after, F::COMMENT_START, offset, F::COMMENT_END)?;
69+
Ok(())
6570
}
6671
}
6772

68-
impl<F: FileFormat> TryFrom<String> for OffsetTemplate<F> {
69-
type Error = Error;
70-
fn try_from(file: String) -> Result<Self, Self::Error> {
71-
let newline_index = file.rfind('\n').ok_or(Error)?;
72-
let s = &file[newline_index+1..];
73-
let s = s.strip_prefix(F::COMMENT_START).ok_or(Error)?;
74-
let s = s.strip_suffix(F::COMMENT_END).ok_or(Error)?;
75-
let offset = serde_json::from_str(&s).map_err(|_| Error)?;
76-
let mut contents = file;
77-
contents.truncate(newline_index);
78-
Ok(OffsetTemplate { format: PhantomData, contents, offset })
79-
}
73+
fn checked_split_at(s: &str, index: usize) -> Option<(&str, &str)> {
74+
s.is_char_boundary(index).then(|| s.split_at(index))
8075
}
8176

82-
mod sealed {
83-
pub trait Sealed { }
77+
impl<F: FileFormat> FromStr for OffsetTemplate<F> {
78+
type Err = Error;
79+
fn from_str(s: &str) -> Result<Self, Self::Err> {
80+
let (s, offset) = s.rsplit_once("\n").ok_or(Error)?;
81+
let offset = offset.strip_prefix(F::COMMENT_START).ok_or(Error)?;
82+
let offset = offset.strip_suffix(F::COMMENT_END).ok_or(Error)?;
83+
let offset: Offset = serde_json::from_str(&offset).map_err(|_| Error)?;
84+
let (before, mut s) = checked_split_at(s, offset.start).ok_or(Error)?;
85+
let mut contents = Vec::default();
86+
let mut sep = "";
87+
for &index in offset.delta.iter() {
88+
let (content, rest) = checked_split_at(s, index).ok_or(Error)?;
89+
s = rest;
90+
let content = content.strip_prefix(sep).ok_or(Error)?;
91+
contents.push(content);
92+
sep = F::SEPARATOR;
93+
}
94+
Ok(OffsetTemplate {
95+
format: PhantomData,
96+
before: before.to_string(),
97+
after: s.to_string(),
98+
contents: contents.into_iter().map(ToString::to_string).collect(),
99+
})
100+
}
84101
}
85102

86-
pub(crate) trait FileFormat: sealed::Sealed {
103+
pub(crate) trait FileFormat {
87104
const COMMENT_START: &'static str;
88105
const COMMENT_END: &'static str;
89106
const SEPARATOR: &'static str;
@@ -92,9 +109,6 @@ pub(crate) trait FileFormat: sealed::Sealed {
92109
#[derive(Debug, Clone)]
93110
pub(crate) struct Html;
94111

95-
/// Suitable for HTML documents
96-
impl sealed::Sealed for Html {}
97-
98112
impl FileFormat for Html {
99113
const COMMENT_START: &'static str = "<!--";
100114
const COMMENT_END: &'static str = "-->";
@@ -104,9 +118,6 @@ impl FileFormat for Html {
104118
#[derive(Debug, Clone)]
105119
pub(crate) struct Js;
106120

107-
/// Suitable for JS files with JSON arrays
108-
impl sealed::Sealed for Js {}
109-
110121
impl FileFormat for Js {
111122
const COMMENT_START: &'static str = "//";
112123
const COMMENT_END: &'static str = "";
@@ -125,6 +136,7 @@ impl fmt::Display for Error {
125136
#[cfg(test)]
126137
mod tests {
127138
use super::*;
139+
use std::str::FromStr;
128140

129141
fn is_comment_js(s: &str) -> bool {
130142
s.starts_with("//")
@@ -139,7 +151,7 @@ mod tests {
139151
let inserts = ["<p>hello</p>", "<p>kind</p>", "<p>world</p>"];
140152
let mut template = OffsetTemplate::<Html>::before_after("", "");
141153
for insert in inserts {
142-
template.append(insert).unwrap();
154+
template.append(insert.to_string());
143155
}
144156
let template = format!("{template}");
145157
let (template, end) = template.rsplit_once("\n").unwrap();
@@ -155,7 +167,7 @@ mod tests {
155167
let after = "</body>";
156168
let mut template = OffsetTemplate::<Html>::before_after(before, after);
157169
for insert in inserts {
158-
template.append(insert).unwrap();
170+
template.append(insert.to_string());
159171
}
160172
let template = format!("{template}");
161173
let (template, end) = template.rsplit_once("\n").unwrap();
@@ -169,7 +181,7 @@ mod tests {
169181
let inserts = ["1", "2", "3"];
170182
let mut template = OffsetTemplate::<Js>::before_after("", "");
171183
for insert in inserts {
172-
template.append(insert).unwrap();
184+
template.append(insert.to_string());
173185
}
174186
let template = format!("{template}");
175187
let (template, end) = template.rsplit_once("\n").unwrap();
@@ -193,7 +205,7 @@ mod tests {
193205
let inserts = ["1", "2", "3"];
194206
let mut template = OffsetTemplate::<Js>::before_after("[", "]");
195207
for insert in inserts {
196-
template.append(insert).unwrap();
208+
template.append(insert.to_string());
197209
}
198210
let template = format!("{template}");
199211
let (template, end) = template.rsplit_once("\n").unwrap();
@@ -207,7 +219,7 @@ mod tests {
207219
let inserts = ["1"];
208220
let mut template = OffsetTemplate::<Js>::magic("[#]", "#").unwrap();
209221
for insert in inserts {
210-
template.append(insert).unwrap();
222+
template.append(insert.to_string());
211223
}
212224
let template = format!("{template}");
213225
let (template, end) = template.rsplit_once("\n").unwrap();
@@ -221,12 +233,12 @@ mod tests {
221233
let inserts = ["1", "2", "3"];
222234
let mut template = OffsetTemplate::<Js>::before_after("[", "]");
223235
for insert in inserts {
224-
template.append(insert).unwrap();
236+
template.append(insert.to_string());
225237
}
226238
let template1 = format!("{template}");
227-
let mut template = OffsetTemplate::<Js>::try_from(template1.clone()).unwrap();
239+
let mut template = OffsetTemplate::<Js>::from_str(&template1).unwrap();
228240
assert_eq!(template1, format!("{template}"));
229-
template.append("4").unwrap();
241+
template.append("4".to_string());
230242
let template = format!("{template}");
231243
let (template, end) = template.rsplit_once("\n").unwrap();
232244
assert_eq!(template, "[1,2,3,4]");
@@ -239,14 +251,35 @@ mod tests {
239251
let before = "<html><head></head><body>";
240252
let after = "</body>";
241253
let mut template = OffsetTemplate::<Html>::before_after(before, after);
242-
template.append(inserts[0]).unwrap();
243-
template.append(inserts[1]).unwrap();
254+
template.append(inserts[0].to_string());
255+
template.append(inserts[1].to_string());
244256
let template = format!("{template}");
245-
let mut template = OffsetTemplate::<Html>::try_from(template).unwrap();
246-
template.append(inserts[2]).unwrap();
257+
let mut template = OffsetTemplate::<Html>::from_str(&template).unwrap();
258+
template.append(inserts[2].to_string());
247259
let template = format!("{template}");
248260
let (template, end) = template.rsplit_once("\n").unwrap();
249261
assert_eq!(template, format!("{before}{}{after}", inserts.join("")));
250262
assert!(is_comment_html(end));
251263
}
264+
265+
#[test]
266+
fn blank_js() {
267+
let inserts = ["1", "2", "3"];
268+
let mut template = OffsetTemplate::<Js>::before_after("", "");
269+
let template = format!("{template}");
270+
let (t, _) = template.rsplit_once("\n").unwrap();
271+
assert_eq!(t, "");
272+
let mut template = OffsetTemplate::<Js>::from_str(&template).unwrap();
273+
for insert in inserts {
274+
template.append(insert.to_string());
275+
}
276+
let template1 = format!("{template}");
277+
let mut template = OffsetTemplate::<Js>::from_str(&template1).unwrap();
278+
assert_eq!(template1, format!("{template}"));
279+
template.append("4".to_string());
280+
let template = format!("{template}");
281+
let (template, end) = template.rsplit_once("\n").unwrap();
282+
assert_eq!(template, "1,2,3,4");
283+
assert!(is_comment_js(end));
284+
}
252285
}

src/librustdoc/html/render/write_shared.rs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ use std::rc::{Rc, Weak};
2424
use std::ffi::OsString;
2525
use std::collections::hash_map::Entry;
2626
use std::iter::once;
27+
use std::str::FromStr;
2728

2829
use indexmap::IndexMap;
2930
use itertools::Itertools;
@@ -72,10 +73,6 @@ pub(crate) fn write_shared(
7273
let crate_name = crate_name.as_str(); // rand
7374
let crate_name_json = SortedJson::serialize(crate_name); // "rand"
7475

75-
if crate_name != "foo" {
76-
panic!("{crate_name} index{} read{} write{}", opt.enable_index_page, opt.read_rendered_cci, opt.write_rendered_cci);
77-
}
78-
7976
let external_crates = hack_get_external_crate_names(cx)?;
8077

8178
let sources = PartsAndLocations::<SourcesPart>::get(cx)?;
@@ -302,9 +299,9 @@ fn hack_get_external_crate_names(cx: &Context<'_>) -> Result<Vec<String>, Error>
302299
let path = cx.dst.join("crates.js");
303300
let Ok(content) = fs::read_to_string(&path) else {
304301
// they didn't emit invocation specific, so we just say there were no crates
305-
return Ok(Vec::default())
302+
return Ok(Vec::default());
306303
};
307-
// this is run only one once so it's fine not to cache it
304+
// this is run only run once so it's fine not to cache it
308305
// dot_matches_new_line false: all crates on same line. greedy: match last bracket
309306
let regex = Regex::new(r"\[.*\]").unwrap();
310307
let Some(content) = regex.find(&content) else {
@@ -861,16 +858,16 @@ fn write_rendered_cci<T: NamedPart + DeserializeOwned + fmt::Display + fmt::Debu
861858
Entry::Vacant(entry) => {
862859
let template = entry.insert(if read_rendered_cci {
863860
match fs::read_to_string(&path) {
864-
Ok(template) => try_err!(OffsetTemplate::try_from(template), &path),
861+
Ok(template) => try_err!(OffsetTemplate::from_str(&template), &path),
865862
Err(e) if e.kind() == io::ErrorKind::NotFound => T::blank_template(cx),
866863
Err(e) => return Err(Error::new(e, &path)),
867864
}
868865
} else {
869866
T::blank_template(cx)
870867
});
871-
try_err!(template.append(&part), &path)
868+
template.append(part);
872869
}
873-
Entry::Occupied(mut t) => try_err!(t.get_mut().append(&part), &path),
870+
Entry::Occupied(mut t) => t.get_mut().append(part),
874871
}
875872
}
876873
// write the merged cci to disk

0 commit comments

Comments
 (0)