1212// See the License for the specific language governing permissions and
1313// limitations under the License.
1414
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 } ;
1621
1722/// Helper structure to build better output of
1823/// [`Matcher::describe`][crate::matcher::Matcher::describe] and
@@ -43,38 +48,40 @@ use std::fmt::{Display, Formatter, Result};
4348/// respectively [`Description::bullet_list`] has been called.
4449#[ derive( Debug , Default ) ]
4550pub struct Description {
46- elements : Vec < String > ,
47- indent_mode : IndentMode ,
48- list_style : ListStyle ,
51+ elements : List ,
52+ initial_indentation : usize ,
4953}
5054
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+ }
5860
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+ }
6668
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+ }
7377
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+ }
7684
77- impl Description {
7885 /// Indents the lines in elements of this description.
7986 ///
8087 /// This operation will be performed lazily when [`self`] is displayed.
@@ -91,27 +98,7 @@ impl Description {
9198 /// # .unwrap();
9299 /// ```
93100 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 }
115102 }
116103
117104 /// Bullet lists the elements of [`self`].
@@ -131,7 +118,7 @@ impl Description {
131118 /// # .unwrap();
132119 /// ```
133120 pub fn bullet_list ( self ) -> Self {
134- Self { list_style : ListStyle :: Bullet , ..self }
121+ Self { elements : self . elements . bullet_list ( ) , ..self }
135122 }
136123
137124 /// Enumerates the elements of [`self`].
@@ -151,7 +138,7 @@ impl Description {
151138 /// # .unwrap();
152139 /// ```
153140 pub fn enumerate ( self ) -> Self {
154- Self { list_style : ListStyle :: Enumerate , ..self }
141+ Self { elements : self . elements . enumerate ( ) , ..self }
155142 }
156143
157144 /// Returns the length of elements.
@@ -163,80 +150,11 @@ impl Description {
163150 pub fn is_empty ( & self ) -> bool {
164151 self . elements . is_empty ( )
165152 }
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- }
194153}
195154
196155impl Display for Description {
197156 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 )
240158 }
241159}
242160
@@ -254,13 +172,15 @@ impl FromIterator<Description> for Description {
254172 where
255173 T : IntoIterator < Item = Description > ,
256174 {
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 ( ) }
258176 }
259177}
260178
261179impl < T : Into < String > > From < T > for Description {
262180 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 ( ) }
264184 }
265185}
266186
@@ -271,124 +191,91 @@ mod tests {
271191 use indoc:: indoc;
272192
273193 #[ 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 ( ) ;
276196 verify_that ! ( description, displays_as( eq( "A B C" ) ) )
277197 }
278198
279199 #[ test]
280- fn description_two_elements ( ) -> Result < ( ) > {
200+ fn renders_two_fragments ( ) -> Result < ( ) > {
281201 let description =
282202 [ "A B C" . to_string ( ) , "D E F" . to_string ( ) ] . into_iter ( ) . collect :: < Description > ( ) ;
283203 verify_that ! ( description, displays_as( eq( "A B C\n D E F" ) ) )
284204 }
285205
286206 #[ 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" ) ) )
290212 }
291213
292214 #[ 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" ) ) )
299220 }
300221
301222 #[ 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" ) ) )
308226 }
309227
310228 #[ 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" ) ) )
324232 }
325233
326234 #[ 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 ( ) ;
329237 verify_that ! ( description, displays_as( eq( "* A B C" ) ) )
330238 }
331239
332240 #[ 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 ( ) ;
338243 verify_that ! ( description, displays_as( eq( "* A B C\n * D E F" ) ) )
339244 }
340245
341246 #[ test]
342- fn description_bullet_single_element_two_lines ( ) -> Result < ( ) > {
247+ fn two_nested_fragments_render_with_bullet_when_bullet_list_enabled ( ) -> Result < ( ) > {
343248 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" ) ) )
356251 }
357252
358253 #[ 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" ) ) )
366257 }
367258
368259 #[ 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 ( ) ;
371262 verify_that ! ( description, displays_as( eq( "0. A B C" ) ) )
372263 }
373264
374265 #[ 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 ( ) ;
380268 verify_that ! ( description, displays_as( eq( "0. A B C\n 1. D E F" ) ) )
381269 }
382270
383271 #[ 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 ( ) ;
387274 verify_that ! ( description, displays_as( eq( "0. A B C\n D E F" ) ) )
388275 }
389276
390277 #[ test]
391- fn description_enumerate_correct_indentation_with_large_index ( ) -> Result < ( ) > {
278+ fn multi_digit_enumeration_renders_with_correct_offset ( ) -> Result < ( ) > {
392279 let description = [ "A B C\n D E F" ; 11 ]
393280 . into_iter ( )
394281 . map ( str:: to_string)
0 commit comments