@@ -456,6 +456,33 @@ impl PartialEq for SimpleTag {
456456 }
457457}
458458
459+ #[ derive( Clone , Debug ) ]
460+ pub struct SimpleBlockTag {
461+ pub func : Arc < Py < PyAny > > ,
462+ pub nodes : Vec < TokenTree > ,
463+ pub at : ( usize , usize ) ,
464+ pub takes_context : bool ,
465+ pub args : Vec < TagElement > ,
466+ pub kwargs : Vec < ( String , TagElement ) > ,
467+ pub target_var : Option < String > ,
468+ }
469+
470+ impl PartialEq for SimpleBlockTag {
471+ fn eq ( & self , other : & Self ) -> bool {
472+ // We use `Arc::ptr_eq` here to avoid needing the `py` token for true
473+ // equality comparison between two `Py` smart pointers.
474+ //
475+ // We only use `eq` in tests, so this concession is acceptable here.
476+ self . at == other. at
477+ && self . takes_context == other. takes_context
478+ && self . args == other. args
479+ && self . kwargs == other. kwargs
480+ && self . target_var == other. target_var
481+ && self . nodes == other. nodes
482+ && Arc :: ptr_eq ( & self . func , & other. func )
483+ }
484+ }
485+
459486#[ derive( Clone , Debug , PartialEq ) ]
460487pub enum Tag {
461488 Autoescape {
@@ -470,6 +497,7 @@ pub enum Tag {
470497 For ( For ) ,
471498 Load ,
472499 SimpleTag ( SimpleTag ) ,
500+ SimpleBlockTag ( SimpleBlockTag ) ,
473501 Url ( Url ) ,
474502}
475503
@@ -482,6 +510,7 @@ enum EndTagType {
482510 Empty ,
483511 EndFor ,
484512 Verbatim ,
513+ Custom ( String ) ,
485514}
486515
487516impl EndTagType {
@@ -494,6 +523,7 @@ impl EndTagType {
494523 Self :: Empty => "empty" ,
495524 Self :: EndFor => "endfor" ,
496525 Self :: Verbatim => "endverbatim" ,
526+ Self :: Custom ( s) => return Cow :: Owned ( s. clone ( ) ) ,
497527 } ;
498528 Cow :: Borrowed ( end_tag)
499529 }
@@ -680,6 +710,12 @@ pub enum ParseError {
680710 #[ label( "here" ) ]
681711 at : SourceSpan ,
682712 } ,
713+ #[ error( "'{name}' must have a first argument of 'content'" ) ]
714+ RequiresContent {
715+ name : String ,
716+ #[ label( "loaded here" ) ]
717+ at : SourceSpan ,
718+ } ,
683719 #[ error(
684720 "'{name}' is decorated with takes_context=True so it must have a first argument of 'context'"
685721 ) ]
@@ -809,7 +845,7 @@ impl LoadToken {
809845 }
810846}
811847
812- #[ derive( Clone ) ]
848+ #[ derive( Debug , Clone ) ]
813849struct SimpleTagContext < ' py > {
814850 func : Bound < ' py , PyAny > ,
815851 function_name : String ,
@@ -825,6 +861,11 @@ struct SimpleTagContext<'py> {
825861#[ derive( Clone ) ]
826862enum TagContext < ' py > {
827863 Simple ( SimpleTagContext < ' py > ) ,
864+ SimpleBlock {
865+ end_tag_name : String ,
866+ context : SimpleTagContext < ' py > ,
867+ } ,
868+ EndSimpleBlock ,
828869}
829870
830871pub struct Parser < ' t , ' l , ' py > {
@@ -1091,6 +1132,21 @@ impl<'t, 'l, 'py> Parser<'t, 'l, 'py> {
10911132 Some ( TagContext :: Simple ( context) ) => {
10921133 Either :: Left ( self . parse_simple_tag ( context, at, parts) ?)
10931134 }
1135+ Some ( TagContext :: SimpleBlock {
1136+ context,
1137+ end_tag_name,
1138+ } ) => Either :: Left ( self . parse_simple_block_tag (
1139+ context. clone ( ) ,
1140+ tag_name. to_string ( ) ,
1141+ end_tag_name. clone ( ) ,
1142+ at,
1143+ parts,
1144+ ) ?) ,
1145+ Some ( TagContext :: EndSimpleBlock ) => Either :: Right ( EndTag {
1146+ end : EndTagType :: Custom ( tag_name. to_string ( ) ) ,
1147+ at,
1148+ parts,
1149+ } ) ,
10941150 None => todo ! ( "{tag_name}" ) ,
10951151 } ,
10961152 } )
@@ -1218,6 +1274,32 @@ impl<'t, 'l, 'py> Parser<'t, 'l, 'py> {
12181274 Ok ( TokenTree :: Tag ( Tag :: SimpleTag ( tag) ) )
12191275 }
12201276
1277+ fn parse_simple_block_tag (
1278+ & mut self ,
1279+ context : SimpleTagContext ,
1280+ tag_name : String ,
1281+ end_tag_name : String ,
1282+ at : ( usize , usize ) ,
1283+ parts : TagParts ,
1284+ ) -> Result < TokenTree , PyParseError > {
1285+ let ( args, kwargs, target_var) = self . parse_custom_tag_parts ( parts, & context) ?;
1286+ let ( nodes, end_tag) = self . parse_until (
1287+ vec ! [ EndTagType :: Custom ( end_tag_name) ] ,
1288+ Cow :: Owned ( tag_name) ,
1289+ at,
1290+ ) ?;
1291+ let tag = SimpleBlockTag {
1292+ func : context. func . clone ( ) . unbind ( ) . into ( ) ,
1293+ nodes,
1294+ at,
1295+ takes_context : context. takes_context ,
1296+ args,
1297+ kwargs,
1298+ target_var,
1299+ } ;
1300+ Ok ( TokenTree :: Tag ( Tag :: SimpleBlockTag ( tag) ) )
1301+ }
1302+
12211303 fn parse_load (
12221304 & mut self ,
12231305 at : ( usize , usize ) ,
@@ -1301,7 +1383,63 @@ impl<'t, 'l, 'py> Parser<'t, 'l, 'py> {
13011383 if closure_names. contains ( & "filename" . to_string ( ) ) {
13021384 todo ! ( "Inclusion tag" )
13031385 } else if closure_names. contains ( & "end_name" . to_string ( ) ) {
1304- todo ! ( "Simple block tag" )
1386+ let defaults_count = get_defaults_count ( & closure_values[ 0 ] ) ?;
1387+ let end_tag_name: String = closure_values[ 1 ] . extract ( ) ?;
1388+ let func = closure_values[ 2 ] . clone ( ) ;
1389+ let function_name = closure_values[ 3 ] . extract ( ) ?;
1390+ let kwonly = closure_values[ 4 ] . extract ( ) ?;
1391+ let kwonly_defaults = get_kwonly_defaults ( & closure_values[ 5 ] ) ?;
1392+ let params: Vec < String > = closure_values[ 6 ] . extract ( ) ?;
1393+ let takes_context = closure_values[ 7 ] . is_truthy ( ) ?;
1394+ let varargs = !closure_values[ 8 ] . is_none ( ) ;
1395+ let varkw = !closure_values[ 9 ] . is_none ( ) ;
1396+
1397+ let params = match takes_context {
1398+ false => {
1399+ if let Some ( param) = params. first ( )
1400+ && param == "content"
1401+ {
1402+ params. iter ( ) . skip ( 1 ) . cloned ( ) . collect ( )
1403+ } else {
1404+ return Err ( ParseError :: RequiresContent {
1405+ name : function_name,
1406+ at : at. into ( ) ,
1407+ }
1408+ . into ( ) ) ;
1409+ }
1410+ }
1411+ true => {
1412+ todo ! ( "context and content" ) ;
1413+ if let Some ( param) = params. first ( )
1414+ && param == "context"
1415+ {
1416+ params. iter ( ) . skip ( 1 ) . cloned ( ) . collect ( )
1417+ } else {
1418+ return Err ( ParseError :: RequiresContext {
1419+ name : function_name,
1420+ at : at. into ( ) ,
1421+ }
1422+ . into ( ) ) ;
1423+ }
1424+ }
1425+ } ;
1426+ // TODO: `end_tag_name already present?
1427+ self . external_tags
1428+ . insert ( end_tag_name. clone ( ) , TagContext :: EndSimpleBlock ) ;
1429+ TagContext :: SimpleBlock {
1430+ end_tag_name,
1431+ context : SimpleTagContext {
1432+ func,
1433+ function_name,
1434+ takes_context,
1435+ params,
1436+ defaults_count,
1437+ varargs,
1438+ kwonly,
1439+ kwonly_defaults,
1440+ varkw,
1441+ } ,
1442+ }
13051443 } else {
13061444 let defaults_count = get_defaults_count ( & closure_values[ 0 ] ) ?;
13071445 let func = closure_values[ 1 ] . clone ( ) ;
0 commit comments