@@ -190,30 +190,35 @@ impl FormatParse for FormatType {
190
190
/// "hello {name:<20}".format(name="test")
191
191
/// ```
192
192
///
193
- /// Format specifications allow nested replacements for dynamic formatting.
193
+ /// Format specifications allow nested placeholders for dynamic formatting.
194
194
/// For example, the following statements are equivalent:
195
195
/// ```python
196
196
/// "hello {name:{fmt}}".format(name="test", fmt="<20")
197
197
/// "hello {name:{align}{width}}".format(name="test", align="<", width="20")
198
198
/// "hello {name:<20{empty}>}".format(name="test", empty="")
199
199
/// ```
200
200
///
201
- /// Nested replacements can include additional format specifiers.
201
+ /// Nested placeholders can include additional format specifiers.
202
202
/// ```python
203
203
/// "hello {name:{fmt:*>}}".format(name="test", fmt="<20")
204
204
/// ```
205
205
///
206
- /// However, replacements can only be singly nested (preserving our sanity).
206
+ /// However, placeholders can only be singly nested (preserving our sanity).
207
207
/// A [`FormatSpecError::PlaceholderRecursionExceeded`] will be raised while parsing in this case.
208
208
/// ```python
209
209
/// "hello {name:{fmt:{not_allowed}}}".format(name="test", fmt="<20") # Syntax error
210
210
/// ```
211
211
///
212
- /// When replacements are present in a format specification, we will parse them and
213
- /// store them in [`FormatSpec`] but will otherwise ignore them if they would introduce
214
- /// an invalid format specification at runtime.
212
+ /// When placeholders are present in a format specification, parsing will return a [`DynamicFormatSpec`]
213
+ /// and avoid attempting to parse any of the clauses. Otherwise, a [`StaticFormatSpec`] will be used.
215
214
#[ derive( Debug , PartialEq ) ]
216
- pub struct FormatSpec {
215
+ pub enum FormatSpec {
216
+ Static ( StaticFormatSpec ) ,
217
+ Dynamic ( DynamicFormatSpec ) ,
218
+ }
219
+
220
+ #[ derive( Debug , PartialEq ) ]
221
+ pub struct StaticFormatSpec {
217
222
// Ex) `!s` in `'{!s}'`
218
223
conversion : Option < FormatConversion > ,
219
224
// Ex) `*` in `'{:*^30}'`
@@ -232,8 +237,12 @@ pub struct FormatSpec {
232
237
precision : Option < usize > ,
233
238
// Ex) `f` in `'{:+f}'`
234
239
format_type : Option < FormatType > ,
240
+ }
241
+
242
+ #[ derive( Debug , PartialEq ) ]
243
+ pub struct DynamicFormatSpec {
235
244
// Ex) `x` and `y` in `'{:*{x},{y}b}'`
236
- replacements : Vec < FormatPart > ,
245
+ pub placeholders : Vec < FormatPart > ,
237
246
}
238
247
239
248
#[ derive( Copy , Clone , Debug , PartialEq , Default ) ]
@@ -318,43 +327,46 @@ fn parse_precision(text: &str) -> Result<(Option<usize>, &str), FormatSpecError>
318
327
} )
319
328
}
320
329
321
- /// Parses a format part within a format spec
322
- fn parse_nested_placeholder < ' a > (
323
- parts : & mut Vec < FormatPart > ,
324
- text : & ' a str ,
325
- ) -> Result < & ' a str , FormatSpecError > {
330
+ /// Parses a placeholder format part within a format specification
331
+ fn parse_nested_placeholder ( text : & str ) -> Result < Option < ( FormatPart , & str ) > , FormatSpecError > {
326
332
match FormatString :: parse_spec ( text, AllowPlaceholderNesting :: No ) {
327
- // Not a nested replacement , OK
328
- Err ( FormatParseError :: MissingStartBracket ) => Ok ( text ) ,
333
+ // Not a nested placeholder , OK
334
+ Err ( FormatParseError :: MissingStartBracket ) => Ok ( None ) ,
329
335
Err ( err) => Err ( FormatSpecError :: InvalidPlaceholder ( err) ) ,
330
- Ok ( ( format_part, text) ) => {
331
- parts. push ( format_part) ;
332
- Ok ( text)
336
+ Ok ( ( format_part, text) ) => Ok ( Some ( ( format_part, text) ) ) ,
337
+ }
338
+ }
339
+
340
+ /// Parse all placeholders in a format specification
341
+ /// If no placeholders are present, an empty vector will be returned
342
+ fn parse_nested_placeholders ( mut text : & str ) -> Result < Vec < FormatPart > , FormatSpecError > {
343
+ let mut placeholders = vec ! [ ] ;
344
+ while let Some ( bracket) = text. find ( '{' ) {
345
+ if let Some ( ( format_part, rest) ) = parse_nested_placeholder ( & text[ bracket..] ) ? {
346
+ text = rest;
347
+ placeholders. push ( format_part) ;
348
+ } else {
349
+ text = & text[ bracket + 1 ..] ;
333
350
}
334
351
}
352
+ Ok ( placeholders)
335
353
}
336
354
337
355
impl FormatSpec {
338
356
pub fn parse ( text : & str ) -> Result < Self , FormatSpecError > {
339
- let mut replacements = vec ! [ ] ;
340
- // get_integer in CPython
341
- let text = parse_nested_placeholder ( & mut replacements, text) ?;
357
+ let placeholders = parse_nested_placeholders ( text) ?;
358
+ if !placeholders. is_empty ( ) {
359
+ return Ok ( FormatSpec :: Dynamic ( DynamicFormatSpec { placeholders } ) ) ;
360
+ }
361
+
342
362
let ( conversion, text) = FormatConversion :: parse ( text) ;
343
- let text = parse_nested_placeholder ( & mut replacements, text) ?;
344
363
let ( mut fill, mut align, text) = parse_fill_and_align ( text) ;
345
- let text = parse_nested_placeholder ( & mut replacements, text) ?;
346
364
let ( sign, text) = FormatSign :: parse ( text) ;
347
- let text = parse_nested_placeholder ( & mut replacements, text) ?;
348
365
let ( alternate_form, text) = parse_alternate_form ( text) ;
349
- let text = parse_nested_placeholder ( & mut replacements, text) ?;
350
366
let ( zero, text) = parse_zero ( text) ;
351
- let text = parse_nested_placeholder ( & mut replacements, text) ?;
352
367
let ( width, text) = parse_number ( text) ?;
353
- let text = parse_nested_placeholder ( & mut replacements, text) ?;
354
368
let ( grouping_option, text) = FormatGrouping :: parse ( text) ;
355
- let text = parse_nested_placeholder ( & mut replacements, text) ?;
356
369
let ( precision, text) = parse_precision ( text) ?;
357
- let text = parse_nested_placeholder ( & mut replacements, text) ?;
358
370
359
371
let ( format_type, _text) = if text. is_empty ( ) {
360
372
( None , text)
@@ -376,7 +388,7 @@ impl FormatSpec {
376
388
align = align. or ( Some ( FormatAlign :: AfterSign ) ) ;
377
389
}
378
390
379
- Ok ( FormatSpec {
391
+ Ok ( FormatSpec :: Static ( StaticFormatSpec {
380
392
conversion,
381
393
fill,
382
394
align,
@@ -386,12 +398,7 @@ impl FormatSpec {
386
398
grouping_option,
387
399
precision,
388
400
format_type,
389
- replacements,
390
- } )
391
- }
392
-
393
- pub fn replacements ( & self ) -> & [ FormatPart ] {
394
- return self . replacements . as_slice ( ) ;
401
+ } ) )
395
402
}
396
403
}
397
404
@@ -437,7 +444,7 @@ impl std::fmt::Display for FormatParseError {
437
444
std:: write!( fmt, "unescaped start bracket in literal" )
438
445
}
439
446
Self :: PlaceholderRecursionExceeded => {
440
- std:: write!( fmt, "multiply nested replacement not allowed" )
447
+ std:: write!( fmt, "multiply nested placeholder not allowed" )
441
448
}
442
449
Self :: UnknownConversion => {
443
450
std:: write!( fmt, "unknown conversion" )
@@ -730,7 +737,7 @@ mod tests {
730
737
731
738
#[ test]
732
739
fn test_width_only ( ) {
733
- let expected = Ok ( FormatSpec {
740
+ let expected = Ok ( FormatSpec :: Static ( StaticFormatSpec {
734
741
conversion : None ,
735
742
fill : None ,
736
743
align : None ,
@@ -740,14 +747,13 @@ mod tests {
740
747
grouping_option : None ,
741
748
precision : None ,
742
749
format_type : None ,
743
- replacements : vec ! [ ] ,
744
- } ) ;
750
+ } ) ) ;
745
751
assert_eq ! ( FormatSpec :: parse( "33" ) , expected) ;
746
752
}
747
753
748
754
#[ test]
749
755
fn test_fill_and_width ( ) {
750
- let expected = Ok ( FormatSpec {
756
+ let expected = Ok ( FormatSpec :: Static ( StaticFormatSpec {
751
757
conversion : None ,
752
758
fill : Some ( '<' ) ,
753
759
align : Some ( FormatAlign :: Right ) ,
@@ -757,45 +763,26 @@ mod tests {
757
763
grouping_option : None ,
758
764
precision : None ,
759
765
format_type : None ,
760
- replacements : vec ! [ ] ,
761
- } ) ;
766
+ } ) ) ;
762
767
assert_eq ! ( FormatSpec :: parse( "<>33" ) , expected) ;
763
768
}
764
769
765
770
#[ test]
766
771
fn test_format_part ( ) {
767
- let expected = Ok ( FormatSpec {
768
- conversion : None ,
769
- fill : None ,
770
- align : None ,
771
- sign : None ,
772
- alternate_form : false ,
773
- width : None ,
774
- grouping_option : None ,
775
- precision : None ,
776
- format_type : None ,
777
- replacements : vec ! [ FormatPart :: Field {
772
+ let expected = Ok ( FormatSpec :: Dynamic ( DynamicFormatSpec {
773
+ placeholders : vec ! [ FormatPart :: Field {
778
774
field_name: "x" . to_string( ) ,
779
775
conversion_spec: None ,
780
776
format_spec: String :: new( ) ,
781
777
} ] ,
782
- } ) ;
778
+ } ) ) ;
783
779
assert_eq ! ( FormatSpec :: parse( "{x}" ) , expected) ;
784
780
}
785
781
786
782
#[ test]
787
- fn test_format_parts ( ) {
788
- let expected = Ok ( FormatSpec {
789
- conversion : None ,
790
- fill : None ,
791
- align : None ,
792
- sign : None ,
793
- alternate_form : false ,
794
- width : None ,
795
- grouping_option : None ,
796
- precision : None ,
797
- format_type : None ,
798
- replacements : vec ! [
783
+ fn test_dynamic_format_spec ( ) {
784
+ let expected = Ok ( FormatSpec :: Dynamic ( DynamicFormatSpec {
785
+ placeholders : vec ! [
799
786
FormatPart :: Field {
800
787
field_name: "x" . to_string( ) ,
801
788
conversion_spec: None ,
@@ -812,34 +799,25 @@ mod tests {
812
799
format_spec: String :: new( ) ,
813
800
} ,
814
801
] ,
815
- } ) ;
802
+ } ) ) ;
816
803
assert_eq ! ( FormatSpec :: parse( "{x}{y:<2}{z}" ) , expected) ;
817
804
}
818
805
819
806
#[ test]
820
- fn test_format_part_with_others ( ) {
821
- let expected = Ok ( FormatSpec {
822
- conversion : None ,
823
- fill : None ,
824
- align : Some ( FormatAlign :: Left ) ,
825
- sign : None ,
826
- alternate_form : false ,
827
- width : Some ( 20 ) ,
828
- grouping_option : None ,
829
- precision : None ,
830
- format_type : Some ( FormatType :: Binary ) ,
831
- replacements : vec ! [ FormatPart :: Field {
807
+ fn test_dynamic_format_spec_with_others ( ) {
808
+ let expected = Ok ( FormatSpec :: Dynamic ( DynamicFormatSpec {
809
+ placeholders : vec ! [ FormatPart :: Field {
832
810
field_name: "x" . to_string( ) ,
833
811
conversion_spec: None ,
834
812
format_spec: String :: new( ) ,
835
813
} ] ,
836
- } ) ;
814
+ } ) ) ;
837
815
assert_eq ! ( FormatSpec :: parse( "<{x}20b" ) , expected) ;
838
816
}
839
817
840
818
#[ test]
841
819
fn test_all ( ) {
842
- let expected = Ok ( FormatSpec {
820
+ let expected = Ok ( FormatSpec :: Static ( StaticFormatSpec {
843
821
conversion : None ,
844
822
fill : Some ( '<' ) ,
845
823
align : Some ( FormatAlign :: Right ) ,
@@ -849,8 +827,7 @@ mod tests {
849
827
grouping_option : Some ( FormatGrouping :: Comma ) ,
850
828
precision : Some ( 11 ) ,
851
829
format_type : Some ( FormatType :: Binary ) ,
852
- replacements : vec ! [ ] ,
853
- } ) ;
830
+ } ) ) ;
854
831
assert_eq ! ( FormatSpec :: parse( "<>-#23,.11b" ) , expected) ;
855
832
}
856
833
@@ -877,7 +854,7 @@ mod tests {
877
854
}
878
855
879
856
#[ test]
880
- fn test_format_parse_nested_replacement ( ) {
857
+ fn test_format_parse_nested_placeholder ( ) {
881
858
let expected = Ok ( FormatString {
882
859
format_parts : vec ! [
883
860
FormatPart :: Literal ( "abcd" . to_owned( ) ) ,
@@ -966,7 +943,15 @@ mod tests {
966
943
) ;
967
944
assert_eq ! (
968
945
FormatSpec :: parse( "{}}" ) ,
969
- Err ( FormatSpecError :: InvalidFormatType )
946
+ // Note this should be an `InvalidFormatType` but we give up
947
+ // on all other parsing validation when we see a placeholder
948
+ Ok ( FormatSpec :: Dynamic ( DynamicFormatSpec {
949
+ placeholders: vec![ FormatPart :: Field {
950
+ field_name: String :: new( ) ,
951
+ conversion_spec: None ,
952
+ format_spec: String :: new( )
953
+ } ]
954
+ } ) )
970
955
) ;
971
956
assert_eq ! (
972
957
FormatSpec :: parse( "{{x}}" ) ,
0 commit comments