1
1
use std:: fmt;
2
2
use std:: marker:: PhantomData ;
3
+ use rustc_data_structures:: fx:: FxHashSet ;
4
+ use std:: str:: FromStr ;
3
5
4
6
use serde:: { Serialize , Deserialize } ;
5
7
6
- /// Append-only templates for lists of items.
8
+ /// Append-only templates for sorted lists of items.
7
9
///
8
10
/// Last line of the rendered output is a comment encoding the next insertion point.
9
11
#[ derive( Debug , Clone ) ]
10
12
pub ( crate ) struct OffsetTemplate < F > {
11
13
format : PhantomData < F > ,
12
- contents : String ,
13
- offset : Offset ,
14
+ before : String ,
15
+ after : String ,
16
+ contents : FxHashSet < String > ,
14
17
}
15
18
16
19
#[ derive( Serialize , Deserialize , Debug , Clone ) ]
17
20
struct Offset {
18
- next_insert : usize ,
19
- empty : bool ,
21
+ start : usize ,
22
+ delta : Vec < usize > ,
20
23
}
21
24
22
25
impl < F > OffsetTemplate < F > {
@@ -34,56 +37,70 @@ impl<F> OffsetTemplate<F> {
34
37
}
35
38
36
39
/// 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 ( ) }
41
44
}
42
45
}
43
46
44
47
impl < F : FileFormat > OffsetTemplate < F > {
45
48
/// 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) ;
58
51
}
59
52
}
60
53
61
54
impl < F : FileFormat > fmt:: Display for OffsetTemplate < F > {
62
55
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 ( ( ) )
65
70
}
66
71
}
67
72
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) )
80
75
}
81
76
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
+ }
84
101
}
85
102
86
- pub ( crate ) trait FileFormat : sealed :: Sealed {
103
+ pub ( crate ) trait FileFormat {
87
104
const COMMENT_START : & ' static str ;
88
105
const COMMENT_END : & ' static str ;
89
106
const SEPARATOR : & ' static str ;
@@ -92,9 +109,6 @@ pub(crate) trait FileFormat: sealed::Sealed {
92
109
#[ derive( Debug , Clone ) ]
93
110
pub ( crate ) struct Html ;
94
111
95
- /// Suitable for HTML documents
96
- impl sealed:: Sealed for Html { }
97
-
98
112
impl FileFormat for Html {
99
113
const COMMENT_START : & ' static str = "<!--" ;
100
114
const COMMENT_END : & ' static str = "-->" ;
@@ -104,9 +118,6 @@ impl FileFormat for Html {
104
118
#[ derive( Debug , Clone ) ]
105
119
pub ( crate ) struct Js ;
106
120
107
- /// Suitable for JS files with JSON arrays
108
- impl sealed:: Sealed for Js { }
109
-
110
121
impl FileFormat for Js {
111
122
const COMMENT_START : & ' static str = "//" ;
112
123
const COMMENT_END : & ' static str = "" ;
@@ -125,6 +136,7 @@ impl fmt::Display for Error {
125
136
#[ cfg( test) ]
126
137
mod tests {
127
138
use super :: * ;
139
+ use std:: str:: FromStr ;
128
140
129
141
fn is_comment_js ( s : & str ) -> bool {
130
142
s. starts_with ( "//" )
@@ -139,7 +151,7 @@ mod tests {
139
151
let inserts = [ "<p>hello</p>" , "<p>kind</p>" , "<p>world</p>" ] ;
140
152
let mut template = OffsetTemplate :: < Html > :: before_after ( "" , "" ) ;
141
153
for insert in inserts {
142
- template. append ( insert) . unwrap ( ) ;
154
+ template. append ( insert. to_string ( ) ) ;
143
155
}
144
156
let template = format ! ( "{template}" ) ;
145
157
let ( template, end) = template. rsplit_once ( "\n " ) . unwrap ( ) ;
@@ -155,7 +167,7 @@ mod tests {
155
167
let after = "</body>" ;
156
168
let mut template = OffsetTemplate :: < Html > :: before_after ( before, after) ;
157
169
for insert in inserts {
158
- template. append ( insert) . unwrap ( ) ;
170
+ template. append ( insert. to_string ( ) ) ;
159
171
}
160
172
let template = format ! ( "{template}" ) ;
161
173
let ( template, end) = template. rsplit_once ( "\n " ) . unwrap ( ) ;
@@ -169,7 +181,7 @@ mod tests {
169
181
let inserts = [ "1" , "2" , "3" ] ;
170
182
let mut template = OffsetTemplate :: < Js > :: before_after ( "" , "" ) ;
171
183
for insert in inserts {
172
- template. append ( insert) . unwrap ( ) ;
184
+ template. append ( insert. to_string ( ) ) ;
173
185
}
174
186
let template = format ! ( "{template}" ) ;
175
187
let ( template, end) = template. rsplit_once ( "\n " ) . unwrap ( ) ;
@@ -193,7 +205,7 @@ mod tests {
193
205
let inserts = [ "1" , "2" , "3" ] ;
194
206
let mut template = OffsetTemplate :: < Js > :: before_after ( "[" , "]" ) ;
195
207
for insert in inserts {
196
- template. append ( insert) . unwrap ( ) ;
208
+ template. append ( insert. to_string ( ) ) ;
197
209
}
198
210
let template = format ! ( "{template}" ) ;
199
211
let ( template, end) = template. rsplit_once ( "\n " ) . unwrap ( ) ;
@@ -207,7 +219,7 @@ mod tests {
207
219
let inserts = [ "1" ] ;
208
220
let mut template = OffsetTemplate :: < Js > :: magic ( "[#]" , "#" ) . unwrap ( ) ;
209
221
for insert in inserts {
210
- template. append ( insert) . unwrap ( ) ;
222
+ template. append ( insert. to_string ( ) ) ;
211
223
}
212
224
let template = format ! ( "{template}" ) ;
213
225
let ( template, end) = template. rsplit_once ( "\n " ) . unwrap ( ) ;
@@ -221,12 +233,12 @@ mod tests {
221
233
let inserts = [ "1" , "2" , "3" ] ;
222
234
let mut template = OffsetTemplate :: < Js > :: before_after ( "[" , "]" ) ;
223
235
for insert in inserts {
224
- template. append ( insert) . unwrap ( ) ;
236
+ template. append ( insert. to_string ( ) ) ;
225
237
}
226
238
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 ( ) ;
228
240
assert_eq ! ( template1, format!( "{template}" ) ) ;
229
- template. append ( "4" ) . unwrap ( ) ;
241
+ template. append ( "4" . to_string ( ) ) ;
230
242
let template = format ! ( "{template}" ) ;
231
243
let ( template, end) = template. rsplit_once ( "\n " ) . unwrap ( ) ;
232
244
assert_eq ! ( template, "[1,2,3,4]" ) ;
@@ -239,14 +251,35 @@ mod tests {
239
251
let before = "<html><head></head><body>" ;
240
252
let after = "</body>" ;
241
253
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 ( ) ) ;
244
256
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 ( ) ) ;
247
259
let template = format ! ( "{template}" ) ;
248
260
let ( template, end) = template. rsplit_once ( "\n " ) . unwrap ( ) ;
249
261
assert_eq ! ( template, format!( "{before}{}{after}" , inserts. join( "" ) ) ) ;
250
262
assert ! ( is_comment_html( end) ) ;
251
263
}
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
+ }
252
285
}
0 commit comments