@@ -19,33 +19,37 @@ pub fn sanitize_name(name: &str) -> String {
1919 name. split_whitespace ( ) . collect :: < Vec < _ > > ( ) . join ( " " )
2020}
2121
22- /// Escape a YAML value by wrapping it in double quotes if it contains
23- /// characters that are special in YAML (e.g. `:`, `#`, `[`, `]`, `{`, `}`).
22+ /// Serialize a YAML scalar value using serde_yaml.
2423pub fn yaml_escape ( value : & str ) -> String {
25- if value. contains ( ':' )
26- || value. contains ( '#' )
27- || value. contains ( '[' )
28- || value. contains ( ']' )
29- || value. contains ( '{' )
30- || value. contains ( '}' )
31- || value. contains ( '"' )
32- || value. contains ( '\'' )
33- || value. contains ( '*' )
34- || value. contains ( '&' )
35- || value. contains ( '!' )
36- || value. contains ( '|' )
37- || value. contains ( '>' )
38- || value. contains ( '%' )
39- || value. contains ( '@' )
40- || value. contains ( '`' )
41- || value. starts_with ( ' ' )
42- || value. ends_with ( ' ' )
43- {
44- // Escape existing double quotes and backslashes, then wrap
45- let escaped = value. replace ( '\\' , "\\ \\ " ) . replace ( '"' , "\\ \" " ) ;
46- format ! ( "\" {}\" " , escaped)
24+ let yaml = serde_yaml:: to_string ( & value) . unwrap_or_else ( |_| value. to_string ( ) ) ;
25+ yaml. trim_end ( ) . to_string ( )
26+ }
27+
28+ /// Build a YAML metadata string from a Recipe's fields.
29+ /// Handles nested values (e.g. nutrition) by parsing pre-formatted YAML blocks.
30+ pub fn metadata_to_yaml ( entries : & [ ( String , String ) ] ) -> String {
31+ use serde_yaml:: Value ;
32+
33+ let mut mapping = serde_yaml:: Mapping :: new ( ) ;
34+
35+ for ( key, value) in entries {
36+ if value. starts_with ( '\n' ) {
37+ // Pre-formatted nested YAML (e.g. nutrition) — parse as nested mapping
38+ let yaml_str = format ! ( "{}:{}" , key, value) ;
39+ if let Ok ( parsed) = serde_yaml:: from_str :: < serde_yaml:: Mapping > ( & yaml_str) {
40+ for ( k, v) in parsed {
41+ mapping. insert ( k, v) ;
42+ }
43+ continue ;
44+ }
45+ }
46+ mapping. insert ( Value :: String ( key. clone ( ) ) , Value :: String ( value. clone ( ) ) ) ;
47+ }
48+
49+ if mapping. is_empty ( ) {
50+ String :: new ( )
4751 } else {
48- value . to_string ( )
52+ serde_yaml :: to_string ( & mapping ) . unwrap_or_default ( )
4953 }
5054}
5155
@@ -60,25 +64,52 @@ mod tests {
6064
6165 #[ test]
6266 fn test_yaml_escape_colon ( ) {
63- assert_eq ! ( yaml_escape( "test : sub" ) , "\" test : sub\" " ) ;
67+ assert_eq ! ( yaml_escape( "test : sub" ) , "' test : sub' " ) ;
6468 }
6569
6670 #[ test]
6771 fn test_yaml_escape_url ( ) {
6872 assert_eq ! (
6973 yaml_escape( "http://example.com/recipe" ) ,
70- "\" http://example.com/recipe\" "
74+ "http://example.com/recipe"
7175 ) ;
7276 }
7377
7478 #[ test]
75- fn test_yaml_escape_with_quotes ( ) {
76- assert_eq ! ( yaml_escape( "say \" hello\" " ) , "\" say \\ \" hello\\ \" \" " ) ;
79+ fn test_yaml_escape_hash ( ) {
80+ assert_eq ! ( yaml_escape( "value # comment" ) , "'value # comment'" ) ;
81+ }
82+
83+ #[ test]
84+ fn test_metadata_to_yaml_simple ( ) {
85+ let entries = vec ! [
86+ ( "source" . to_string( ) , "http://example.com" . to_string( ) ) ,
87+ ( "servings" . to_string( ) , "4" . to_string( ) ) ,
88+ ] ;
89+ let yaml = metadata_to_yaml ( & entries) ;
90+ assert ! ( yaml. contains( "source: http://example.com" ) ) ;
91+ assert ! ( yaml. contains( "servings: '4'" ) ) ;
7792 }
7893
7994 #[ test]
80- fn test_yaml_escape_hash ( ) {
81- assert_eq ! ( yaml_escape( "value # comment" ) , "\" value # comment\" " ) ;
95+ fn test_metadata_to_yaml_with_colon ( ) {
96+ let entries = vec ! [ ( "description" . to_string( ) , "test : sub" . to_string( ) ) ] ;
97+ let yaml = metadata_to_yaml ( & entries) ;
98+ assert ! ( yaml. contains( "description: 'test : sub'" ) ) ;
99+ }
100+
101+ #[ test]
102+ fn test_metadata_to_yaml_nested ( ) {
103+ let entries = vec ! [ (
104+ "nutrition" . to_string( ) ,
105+ "\n calories: 330 calories\n fat: 18 grams fat" . to_string( ) ,
106+ ) ] ;
107+ let yaml = metadata_to_yaml ( & entries) ;
108+ assert ! ( yaml. contains( "nutrition:" ) ) ;
109+ assert ! ( yaml. contains( "calories: 330 calories" ) ) ;
110+ assert ! ( yaml. contains( "fat: 18 grams fat" ) ) ;
111+ // Should NOT be quoted as a single string
112+ assert ! ( !yaml. contains( "\" " ) ) ;
82113 }
83114
84115 #[ test]
0 commit comments