12
12
// See the License for the specific language governing permissions and
13
13
// limitations under the License.
14
14
15
- use std:: fmt:: { Display , Formatter , Result } ;
15
+ use std:: {
16
+ borrow:: Cow ,
17
+ fmt:: { Display , Formatter , Result } ,
18
+ } ;
19
+
20
+ use crate :: internal:: description_renderer:: { List , INDENTATION_SIZE } ;
16
21
17
22
/// Helper structure to build better output of
18
23
/// [`Matcher::describe`][crate::matcher::Matcher::describe] and
@@ -43,38 +48,40 @@ use std::fmt::{Display, Formatter, Result};
43
48
/// respectively [`Description::bullet_list`] has been called.
44
49
#[ derive( Debug , Default ) ]
45
50
pub struct Description {
46
- elements : Vec < String > ,
47
- indent_mode : IndentMode ,
48
- list_style : ListStyle ,
51
+ elements : List ,
52
+ initial_indentation : usize ,
49
53
}
50
54
51
- #[ derive( Debug , Default ) ]
52
- enum IndentMode {
53
- #[ default]
54
- NoIndent ,
55
- EveryLine ,
56
- AllExceptFirstLine ,
57
- }
55
+ impl Description {
56
+ /// Returns a new empty [`Description`].
57
+ pub fn new ( ) -> Self {
58
+ Default :: default ( )
59
+ }
58
60
59
- # [ derive ( Debug , Default ) ]
60
- enum ListStyle {
61
- # [ default ]
62
- NoList ,
63
- Bullet ,
64
- Enumerate ,
65
- }
61
+ /// Appends a block of text to this instance.
62
+ ///
63
+ /// The block is indented uniformly when this instance is rendered.
64
+ pub fn text ( mut self , text : impl Into < Cow < ' static , str > > ) -> Self {
65
+ self . elements . push_literal ( text . into ( ) ) ;
66
+ self
67
+ }
66
68
67
- struct IndentationSizes {
68
- first_line_indent : usize ,
69
- first_line_of_element_indent : usize ,
70
- enumeration_padding : usize ,
71
- other_line_indent : usize ,
72
- }
69
+ /// Appends a nested [`Description`] to this instance.
70
+ ///
71
+ /// The nested [`Description`] `inner` is indented uniformly at the next level of indentation
72
+ /// when this instance is rendered.
73
+ pub fn nested ( mut self , inner : Description ) -> Self {
74
+ self . elements . push_nested ( inner. elements ) ;
75
+ self
76
+ }
73
77
74
- /// Number of space used to indent lines when no alignement is required.
75
- const INDENTATION_SIZE : usize = 2 ;
78
+ /// Appends all [`Description`] in the given sequence `inner` to this instance.
79
+ ///
80
+ /// Each element is treated as a nested [`Description`] in the sense of [`Self::nested`].
81
+ pub fn collect ( self , inner : impl IntoIterator < Item = Description > ) -> Self {
82
+ inner. into_iter ( ) . fold ( self , |outer, inner| outer. nested ( inner) )
83
+ }
76
84
77
- impl Description {
78
85
/// Indents the lines in elements of this description.
79
86
///
80
87
/// This operation will be performed lazily when [`self`] is displayed.
@@ -91,27 +98,7 @@ impl Description {
91
98
/// # .unwrap();
92
99
/// ```
93
100
pub fn indent ( self ) -> Self {
94
- Self { indent_mode : IndentMode :: EveryLine , ..self }
95
- }
96
-
97
- /// Indents the lines in elements of this description except for the first
98
- /// line.
99
- ///
100
- /// This is similar to [`Self::indent`] except that the first line is not
101
- /// indented. This is useful when the first line has already been indented
102
- /// in the output.
103
- ///
104
- /// For example:
105
- ///
106
- /// ```
107
- /// # use googletest::prelude::*;
108
- /// # use googletest::description::Description;
109
- /// let description = std::iter::once("A B C\nD E F".to_string()).collect::<Description>();
110
- /// verify_that!(description.indent_except_first_line(), displays_as(eq("A B C\n D E F")))
111
- /// # .unwrap();
112
- /// ```
113
- pub fn indent_except_first_line ( self ) -> Self {
114
- Self { indent_mode : IndentMode :: AllExceptFirstLine , ..self }
101
+ Self { initial_indentation : INDENTATION_SIZE , ..self }
115
102
}
116
103
117
104
/// Bullet lists the elements of [`self`].
@@ -131,7 +118,7 @@ impl Description {
131
118
/// # .unwrap();
132
119
/// ```
133
120
pub fn bullet_list ( self ) -> Self {
134
- Self { list_style : ListStyle :: Bullet , ..self }
121
+ Self { elements : self . elements . bullet_list ( ) , ..self }
135
122
}
136
123
137
124
/// Enumerates the elements of [`self`].
@@ -151,7 +138,7 @@ impl Description {
151
138
/// # .unwrap();
152
139
/// ```
153
140
pub fn enumerate ( self ) -> Self {
154
- Self { list_style : ListStyle :: Enumerate , ..self }
141
+ Self { elements : self . elements . enumerate ( ) , ..self }
155
142
}
156
143
157
144
/// Returns the length of elements.
@@ -163,80 +150,11 @@ impl Description {
163
150
pub fn is_empty ( & self ) -> bool {
164
151
self . elements . is_empty ( )
165
152
}
166
-
167
- fn indentation_sizes ( & self ) -> IndentationSizes {
168
- let first_line_indent =
169
- if matches ! ( self . indent_mode, IndentMode :: EveryLine ) { INDENTATION_SIZE } else { 0 } ;
170
- let first_line_of_element_indent =
171
- if !matches ! ( self . indent_mode, IndentMode :: NoIndent ) { INDENTATION_SIZE } else { 0 } ;
172
- // Number of digit of the last index. For instance, an array of length 13 will
173
- // have 12 as last index (we start at 0), which have a digit size of 2.
174
- let enumeration_padding = if self . elements . len ( ) > 1 {
175
- ( ( self . elements . len ( ) - 1 ) as f64 ) . log10 ( ) . floor ( ) as usize + 1
176
- } else {
177
- // Avoid negative logarithm when there is only 0 or 1 element.
178
- 1
179
- } ;
180
-
181
- let other_line_indent = first_line_of_element_indent
182
- + match self . list_style {
183
- ListStyle :: NoList => 0 ,
184
- ListStyle :: Bullet => "* " . len ( ) ,
185
- ListStyle :: Enumerate => enumeration_padding + ". " . len ( ) ,
186
- } ;
187
- IndentationSizes {
188
- first_line_indent,
189
- first_line_of_element_indent,
190
- enumeration_padding,
191
- other_line_indent,
192
- }
193
- }
194
153
}
195
154
196
155
impl Display for Description {
197
156
fn fmt ( & self , f : & mut Formatter ) -> Result {
198
- let IndentationSizes {
199
- mut first_line_indent,
200
- first_line_of_element_indent,
201
- enumeration_padding,
202
- other_line_indent,
203
- } = self . indentation_sizes ( ) ;
204
-
205
- let mut first = true ;
206
- for ( idx, element) in self . elements . iter ( ) . enumerate ( ) {
207
- let mut lines = element. lines ( ) ;
208
- if let Some ( line) = lines. next ( ) {
209
- if first {
210
- first = false ;
211
- } else {
212
- writeln ! ( f) ?;
213
- }
214
- match self . list_style {
215
- ListStyle :: NoList => {
216
- write ! ( f, "{:first_line_indent$}{line}" , "" ) ?;
217
- }
218
- ListStyle :: Bullet => {
219
- write ! ( f, "{:first_line_indent$}* {line}" , "" ) ?;
220
- }
221
- ListStyle :: Enumerate => {
222
- write ! (
223
- f,
224
- "{:first_line_indent$}{:>enumeration_padding$}. {line}" ,
225
- "" , idx,
226
- ) ?;
227
- }
228
- }
229
- }
230
- for line in lines {
231
- writeln ! ( f) ?;
232
- write ! ( f, "{:other_line_indent$}{line}" , "" ) ?;
233
- }
234
- if element. ends_with ( "\n " ) {
235
- writeln ! ( f) ?;
236
- }
237
- first_line_indent = first_line_of_element_indent;
238
- }
239
- Ok ( ( ) )
157
+ self . elements . render ( f, self . initial_indentation )
240
158
}
241
159
}
242
160
@@ -254,13 +172,15 @@ impl FromIterator<Description> for Description {
254
172
where
255
173
T : IntoIterator < Item = Description > ,
256
174
{
257
- Self { elements : iter. into_iter ( ) . map ( |s| format ! ( "{s}" ) ) . collect ( ) , ..Default :: default ( ) }
175
+ Self { elements : iter. into_iter ( ) . map ( |s| s . elements ) . collect ( ) , ..Default :: default ( ) }
258
176
}
259
177
}
260
178
261
179
impl < T : Into < String > > From < T > for Description {
262
180
fn from ( value : T ) -> Self {
263
- Self { elements : vec ! [ value. into( ) ] , ..Default :: default ( ) }
181
+ let mut elements = List :: default ( ) ;
182
+ elements. push_literal ( value. into ( ) . into ( ) ) ;
183
+ Self { elements, ..Default :: default ( ) }
264
184
}
265
185
}
266
186
@@ -271,124 +191,91 @@ mod tests {
271
191
use indoc:: indoc;
272
192
273
193
#[ test]
274
- fn description_single_element ( ) -> Result < ( ) > {
275
- let description = [ "A B C" . to_string ( ) ] . into_iter ( ) . collect :: < Description > ( ) ;
194
+ fn renders_single_fragment ( ) -> Result < ( ) > {
195
+ let description: Description = "A B C" . into ( ) ;
276
196
verify_that ! ( description, displays_as( eq( "A B C" ) ) )
277
197
}
278
198
279
199
#[ test]
280
- fn description_two_elements ( ) -> Result < ( ) > {
200
+ fn renders_two_fragments ( ) -> Result < ( ) > {
281
201
let description =
282
202
[ "A B C" . to_string ( ) , "D E F" . to_string ( ) ] . into_iter ( ) . collect :: < Description > ( ) ;
283
203
verify_that ! ( description, displays_as( eq( "A B C\n D E F" ) ) )
284
204
}
285
205
286
206
#[ test]
287
- fn description_indent_single_element ( ) -> Result < ( ) > {
288
- let description = [ "A B C" . to_string ( ) ] . into_iter ( ) . collect :: < Description > ( ) . indent ( ) ;
289
- verify_that ! ( description, displays_as( eq( " A B C" ) ) )
207
+ fn nested_description_is_indented ( ) -> Result < ( ) > {
208
+ let description = Description :: new ( )
209
+ . text ( "Header" )
210
+ . nested ( [ "A B C" . to_string ( ) ] . into_iter ( ) . collect :: < Description > ( ) ) ;
211
+ verify_that ! ( description, displays_as( eq( "Header\n A B C" ) ) )
290
212
}
291
213
292
214
#[ test]
293
- fn description_indent_two_elements ( ) -> Result < ( ) > {
294
- let description = [ "A B C" . to_string ( ) , "D E F" . to_string ( ) ]
295
- . into_iter ( )
296
- . collect :: < Description > ( )
297
- . indent ( ) ;
298
- verify_that ! ( description, displays_as( eq( " A B C\n D E F" ) ) )
215
+ fn nested_description_indents_two_elements ( ) -> Result < ( ) > {
216
+ let description = Description :: new ( ) . text ( "Header" ) . nested (
217
+ [ "A B C" . to_string ( ) , "D E F" . to_string ( ) ] . into_iter ( ) . collect :: < Description > ( ) ,
218
+ ) ;
219
+ verify_that ! ( description, displays_as( eq( "Header\n A B C\n D E F" ) ) )
299
220
}
300
221
301
222
#[ test]
302
- fn description_indent_two_elements_except_first_line ( ) -> Result < ( ) > {
303
- let description = [ "A B C" . to_string ( ) , "D E F" . to_string ( ) ]
304
- . into_iter ( )
305
- . collect :: < Description > ( )
306
- . indent_except_first_line ( ) ;
307
- verify_that ! ( description, displays_as( eq( "A B C\n D E F" ) ) )
223
+ fn nested_description_indents_one_element_on_two_lines ( ) -> Result < ( ) > {
224
+ let description = Description :: new ( ) . text ( "Header" ) . nested ( "A B C\n D E F" . into ( ) ) ;
225
+ verify_that ! ( description, displays_as( eq( "Header\n A B C\n D E F" ) ) )
308
226
}
309
227
310
228
#[ test]
311
- fn description_indent_single_element_two_lines ( ) -> Result < ( ) > {
312
- let description =
313
- [ "A B C\n D E F" . to_string ( ) ] . into_iter ( ) . collect :: < Description > ( ) . indent ( ) ;
314
- verify_that ! ( description, displays_as( eq( " A B C\n D E F" ) ) )
315
- }
316
-
317
- #[ test]
318
- fn description_indent_single_element_two_lines_except_first_line ( ) -> Result < ( ) > {
319
- let description = [ "A B C\n D E F" . to_string ( ) ]
320
- . into_iter ( )
321
- . collect :: < Description > ( )
322
- . indent_except_first_line ( ) ;
323
- verify_that ! ( description, displays_as( eq( "A B C\n D E F" ) ) )
229
+ fn single_fragment_renders_with_bullet_when_bullet_list_enabled ( ) -> Result < ( ) > {
230
+ let description = Description :: new ( ) . text ( "A B C" ) . bullet_list ( ) ;
231
+ verify_that ! ( description, displays_as( eq( "* A B C" ) ) )
324
232
}
325
233
326
234
#[ test]
327
- fn description_bullet_single_element ( ) -> Result < ( ) > {
328
- let description = [ "A B C" . to_string ( ) ] . into_iter ( ) . collect :: < Description > ( ) . bullet_list ( ) ;
235
+ fn single_nested_fragment_renders_with_bullet_when_bullet_list_enabled ( ) -> Result < ( ) > {
236
+ let description = Description :: new ( ) . nested ( "A B C" . into ( ) ) . bullet_list ( ) ;
329
237
verify_that ! ( description, displays_as( eq( "* A B C" ) ) )
330
238
}
331
239
332
240
#[ test]
333
- fn description_bullet_two_elements ( ) -> Result < ( ) > {
334
- let description = [ "A B C" . to_string ( ) , "D E F" . to_string ( ) ]
335
- . into_iter ( )
336
- . collect :: < Description > ( )
337
- . bullet_list ( ) ;
241
+ fn two_fragments_render_with_bullet_when_bullet_list_enabled ( ) -> Result < ( ) > {
242
+ let description = Description :: new ( ) . text ( "A B C" ) . text ( "D E F" ) . bullet_list ( ) ;
338
243
verify_that ! ( description, displays_as( eq( "* A B C\n * D E F" ) ) )
339
244
}
340
245
341
246
#[ test]
342
- fn description_bullet_single_element_two_lines ( ) -> Result < ( ) > {
247
+ fn two_nested_fragments_render_with_bullet_when_bullet_list_enabled ( ) -> Result < ( ) > {
343
248
let description =
344
- [ "A B C\n D E F" . to_string ( ) ] . into_iter ( ) . collect :: < Description > ( ) . bullet_list ( ) ;
345
- verify_that ! ( description, displays_as( eq( "* A B C\n D E F" ) ) )
346
- }
347
-
348
- #[ test]
349
- fn description_bullet_single_element_two_lines_indent_except_first_line ( ) -> Result < ( ) > {
350
- let description = [ "A B C\n D E F" . to_string ( ) ]
351
- . into_iter ( )
352
- . collect :: < Description > ( )
353
- . bullet_list ( )
354
- . indent_except_first_line ( ) ;
355
- verify_that ! ( description, displays_as( eq( "* A B C\n D E F" ) ) )
249
+ Description :: new ( ) . nested ( "A B C" . into ( ) ) . nested ( "D E F" . into ( ) ) . bullet_list ( ) ;
250
+ verify_that ! ( description, displays_as( eq( "* A B C\n * D E F" ) ) )
356
251
}
357
252
358
253
#[ test]
359
- fn description_bullet_two_elements_indent_except_first_line ( ) -> Result < ( ) > {
360
- let description = [ "A B C" . to_string ( ) , "D E F" . to_string ( ) ]
361
- . into_iter ( )
362
- . collect :: < Description > ( )
363
- . bullet_list ( )
364
- . indent_except_first_line ( ) ;
365
- verify_that ! ( description, displays_as( eq( "* A B C\n * D E F" ) ) )
254
+ fn single_fragment_with_more_than_one_line_renders_with_one_bullet ( ) -> Result < ( ) > {
255
+ let description = Description :: new ( ) . text ( "A B C\n D E F" ) . bullet_list ( ) ;
256
+ verify_that ! ( description, displays_as( eq( "* A B C\n D E F" ) ) )
366
257
}
367
258
368
259
#[ test]
369
- fn description_enumerate_single_element ( ) -> Result < ( ) > {
370
- let description = [ "A B C" . to_string ( ) ] . into_iter ( ) . collect :: < Description > ( ) . enumerate ( ) ;
260
+ fn single_fragment_renders_with_enumeration_when_enumerate_enabled ( ) -> Result < ( ) > {
261
+ let description = Description :: new ( ) . text ( "A B C" ) . enumerate ( ) ;
371
262
verify_that ! ( description, displays_as( eq( "0. A B C" ) ) )
372
263
}
373
264
374
265
#[ test]
375
- fn description_enumerate_two_elements ( ) -> Result < ( ) > {
376
- let description = [ "A B C" . to_string ( ) , "D E F" . to_string ( ) ]
377
- . into_iter ( )
378
- . collect :: < Description > ( )
379
- . enumerate ( ) ;
266
+ fn two_fragments_render_with_enumeration_when_enumerate_enabled ( ) -> Result < ( ) > {
267
+ let description = Description :: new ( ) . text ( "A B C" ) . text ( "D E F" ) . enumerate ( ) ;
380
268
verify_that ! ( description, displays_as( eq( "0. A B C\n 1. D E F" ) ) )
381
269
}
382
270
383
271
#[ test]
384
- fn description_enumerate_single_element_two_lines ( ) -> Result < ( ) > {
385
- let description =
386
- [ "A B C\n D E F" . to_string ( ) ] . into_iter ( ) . collect :: < Description > ( ) . enumerate ( ) ;
272
+ fn single_fragment_with_two_lines_renders_with_one_enumeration_label ( ) -> Result < ( ) > {
273
+ let description = Description :: new ( ) . text ( "A B C\n D E F" ) . enumerate ( ) ;
387
274
verify_that ! ( description, displays_as( eq( "0. A B C\n D E F" ) ) )
388
275
}
389
276
390
277
#[ test]
391
- fn description_enumerate_correct_indentation_with_large_index ( ) -> Result < ( ) > {
278
+ fn multi_digit_enumeration_renders_with_correct_offset ( ) -> Result < ( ) > {
392
279
let description = [ "A B C\n D E F" ; 11 ]
393
280
. into_iter ( )
394
281
. map ( str:: to_string)
0 commit comments