@@ -17,6 +17,7 @@ use crate::render::pin::PinArgs;
1717pub use crate :: render:: pin:: { Pin , PinExpression } ;
1818pub use crate :: selectors:: SelectorConfig ;
1919
20+ use super :: custom_yaml:: ScalarNode ;
2021use super :: parser:: { Dependency , PinCompatible , PinSubpackage } ;
2122use super :: variable:: Variable ;
2223
@@ -54,6 +55,32 @@ pub struct Jinja {
5455 context : BTreeMap < String , Value > ,
5556}
5657
58+ /// If we have a template that is _only_ an expression, we want to strip the
59+ /// `${{` and `}}` from it so that we can evaluate it as an expression instead of
60+ /// rendering it as a string.
61+ ///
62+ /// The function checks for:
63+ /// - Whitespace before/after the expression
64+ /// - Single expression with no nested expressions
65+ /// - Proper opening `${{` and closing `}}`
66+ fn strip_expression ( template : & str ) -> Option < & str > {
67+ let trimmed = template. trim ( ) ;
68+ if !trimmed. starts_with ( "${{" ) || !trimmed. ends_with ( "}}" ) {
69+ return None ;
70+ }
71+
72+ // Extract content between ${{ and }}
73+ let content = & trimmed[ 3 ..trimmed. len ( ) - 2 ] ;
74+ let content = content. trim ( ) ;
75+
76+ // Check for nested expressions
77+ if content. contains ( "${{" ) || content. contains ( "}}" ) {
78+ return None ;
79+ }
80+
81+ Some ( content)
82+ }
83+
5784impl Jinja {
5885 /// Create a new Jinja instance with the given selector configuration.
5986 pub fn new ( config : SelectorConfig ) -> Self {
@@ -94,26 +121,42 @@ impl Jinja {
94121 & mut self . context
95122 }
96123
97- /// Render a template with the current context.
98- pub fn render_str ( & self , template : & str ) -> Result < ( String , bool ) , minijinja:: Error > {
99- if template. starts_with ( "${{" ) && template. ends_with ( "}}" ) {
100- // render as expression so that we know the type of the result, and can stringify accordingly
101- // If we find something like "${{ foo }}" then we want to evaluate it type-safely and make sure that the MiniJinja type is kept
102- let tmplt = & template[ 3 ..template. len ( ) - 2 ] ;
103- let expr = self . env . compile_expression ( tmplt) ?;
104- let evaled = expr. eval ( self . context ( ) ) ?;
105- if let Some ( s) = evaled. to_str ( ) {
106- // Make sure that the string stays a string by returning can_coerce: false
107- return Ok ( ( s. to_string ( ) , false ) ) ;
108- } else {
109- return Ok ( ( evaled. to_string ( ) , true ) ) ;
110- }
124+ /// Render the given template to a Jinja value.
125+ pub fn render_to_value (
126+ & self ,
127+ template : & ScalarNode ,
128+ ) -> Result < ( Value , bool ) , minijinja:: Error > {
129+ if let Some ( simple_expr) = strip_expression ( template) {
130+ // render as expression so that we know the type of the result
131+ let expr = self . env . compile_expression ( simple_expr) ?;
132+ return Ok ( ( expr. eval ( self . context ( ) ) ?, true ) ) ;
111133 }
112134
135+ // Otherwise just render it as string
113136 let rendered = self . env . render_str ( template, & self . context ) ?;
114- Ok ( ( rendered, !template. contains ( "${{" ) ) )
137+ Ok ( ( Value :: from ( rendered) , !template. contains ( "${{" ) ) )
115138 }
116139
140+ /// Render a template with the current context.
141+ // pub fn render_str(&self, template: &str) -> Result<(String, bool), minijinja::Error> {
142+ // if template.starts_with("${{") && template.ends_with("}}") {
143+ // // render as expression so that we know the type of the result, and can stringify accordingly
144+ // // If we find something like "${{ foo }}" then we want to evaluate it type-safely and make sure that the MiniJinja type is kept
145+ // let tmplt = &template[3..template.len() - 2];
146+ // let expr = self.env.compile_expression(tmplt)?;
147+ // let evaled = expr.eval(self.context())?;
148+ // if let Some(s) = evaled.to_str() {
149+ // // Make sure that the string stays a string by returning can_coerce: false
150+ // return Ok((s.to_string(), false));
151+ // } else {
152+ // return Ok((evaled.to_string(), true));
153+ // }
154+ // }
155+
156+ // let rendered = self.env.render_str(template, &self.context)?;
157+ // Ok((rendered, !template.contains("${{")))
158+ // }
159+
117160 /// Render, compile and evaluate a expr string with the current context.
118161 pub fn eval ( & self , str : & str ) -> Result < Value , minijinja:: Error > {
119162 let expr = self . env . compile_expression ( str) ?;
@@ -1381,4 +1424,23 @@ mod tests {
13811424 default_compiler( platform, "cuda" ) . unwrap( ) . to_string( )
13821425 ) ;
13831426 }
1427+
1428+ #[ test]
1429+ fn test_strip_expression ( ) {
1430+ // Valid cases
1431+ assert_eq ! ( strip_expression( "${{ expr }}" ) , Some ( "expr" ) ) ;
1432+ assert_eq ! ( strip_expression( " ${{ expr }} " ) , Some ( "expr" ) ) ;
1433+ assert_eq ! ( strip_expression( "${{expr}}" ) , Some ( "expr" ) ) ;
1434+ assert_eq ! (
1435+ strip_expression( "${{ expr with spaces }}" ) ,
1436+ Some ( "expr with spaces" )
1437+ ) ;
1438+
1439+ // Invalid cases
1440+ assert_eq ! ( strip_expression( "not an expression" ) , None ) ;
1441+ assert_eq ! ( strip_expression( "${{ nested ${{ expr }} }}" ) , None ) ;
1442+ assert_eq ! ( strip_expression( "${{ unmatched" ) , None ) ;
1443+ assert_eq ! ( strip_expression( "unmatched }}" ) , None ) ;
1444+ assert_eq ! ( strip_expression( "text ${{ expr }} text" ) , None ) ;
1445+ }
13841446}
0 commit comments