@@ -49,15 +49,20 @@ pub fn parse_template(template: &str) -> Result<Vec<StringOp>, String> {
4949}
5050
5151fn resolve_index ( idx : isize , len : usize ) -> usize {
52- let len = len as isize ;
53- let mut i = if idx < 0 { len + idx } else { idx } ;
54- if i < 0 {
55- i = 0 ;
52+ if len == 0 {
53+ return 0 ;
5654 }
57- if i > len {
58- i = len;
55+
56+ let len_i = len as isize ;
57+ let resolved = if idx < 0 { len_i + idx } else { idx } ;
58+
59+ if resolved < 0 {
60+ 0
61+ } else if resolved > len_i {
62+ len - 1
63+ } else {
64+ resolved as usize
5965 }
60- i as usize
6166}
6267
6368fn apply_range < T : Clone > ( items : & [ T ] , range : & RangeSpec ) -> Vec < T > {
@@ -128,14 +133,15 @@ fn unescape(s: &str) -> String {
128133 out. push ( '/' ) ;
129134 chars. next ( ) ;
130135 }
136+ Some ( '|' ) => {
137+ out. push ( '|' ) ;
138+ chars. next ( ) ;
139+ }
131140 Some ( & next) => {
132- // For any other escaped character, just output the character without the backslash
133141 out. push ( next) ;
134142 chars. next ( ) ;
135143 }
136- None => {
137- out. push ( '\\' ) ;
138- }
144+ None => out. push ( '\\' ) ,
139145 }
140146 } else {
141147 out. push ( c) ;
@@ -146,7 +152,7 @@ fn unescape(s: &str) -> String {
146152
147153pub fn apply_ops ( input : & str , ops : & [ StringOp ] ) -> Result < String , String > {
148154 let mut val = Value :: Str ( input. to_string ( ) ) ;
149- let mut last_split_sep : Option < String > = None ;
155+ let mut default_sep = " " . to_string ( ) ; // Clear default
150156 for op in ops {
151157 match op {
152158 StringOp :: Split { sep, range } => {
@@ -157,7 +163,7 @@ pub fn apply_ops(input: &str, ops: &[StringOp]) -> Result<String, String> {
157163 . flat_map ( |s| s. split ( sep) . map ( |s| s. to_string ( ) ) )
158164 . collect ( ) ,
159165 } ;
160- last_split_sep = Some ( sep. clone ( ) ) ;
166+ default_sep = sep. clone ( ) ; // Track for final output
161167 let result = apply_range ( & parts, range) ;
162168 val = Value :: List ( result) ;
163169 }
@@ -188,7 +194,7 @@ pub fn apply_ops(input: &str, ops: &[StringOp]) -> Result<String, String> {
188194 list. join ( & unescaped_sep)
189195 } ;
190196 val = Value :: Str ( joined) ;
191- last_split_sep = Some ( unescaped_sep) ; // Keep track of the join separator
197+ default_sep = unescaped_sep. clone ( ) ; // Update default
192198 }
193199 Value :: Str ( s) => {
194200 val = Value :: Str ( s. clone ( ) ) ;
@@ -304,7 +310,7 @@ pub fn apply_ops(input: &str, ops: &[StringOp]) -> Result<String, String> {
304310 Value :: Str ( s) => val = Value :: Str ( format ! ( "{}{}" , s, suffix) ) ,
305311 Value :: List ( list) => {
306312 if list. is_empty ( ) {
307- val = Value :: Str ( suffix. clone ( ) ) ;
313+ val = Value :: List ( vec ! [ suffix. clone( ) ] ) ; // Create single-item list
308314 } else {
309315 val =
310316 Value :: List ( list. iter ( ) . map ( |s| format ! ( "{}{}" , s, suffix) ) . collect ( ) ) ;
@@ -315,7 +321,7 @@ pub fn apply_ops(input: &str, ops: &[StringOp]) -> Result<String, String> {
315321 Value :: Str ( s) => val = Value :: Str ( format ! ( "{}{}" , prefix, s) ) ,
316322 Value :: List ( list) => {
317323 if list. is_empty ( ) {
318- val = Value :: Str ( prefix. clone ( ) ) ;
324+ val = Value :: List ( vec ! [ prefix. clone( ) ] ) ; // Create single-item list
319325 } else {
320326 val =
321327 Value :: List ( list. iter ( ) . map ( |s| format ! ( "{}{}" , prefix, s) ) . collect ( ) ) ;
@@ -333,7 +339,7 @@ pub fn apply_ops(input: &str, ops: &[StringOp]) -> Result<String, String> {
333339 if list. is_empty ( ) {
334340 String :: new ( )
335341 } else {
336- list. join ( last_split_sep . as_deref ( ) . unwrap_or ( " " ) )
342+ list. join ( & default_sep )
337343 }
338344 }
339345 } )
@@ -829,4 +835,162 @@ mod tests {
829835 assert_eq ! ( process( "word" , "{0..}" ) . unwrap( ) , "word" ) ;
830836 assert_eq ! ( process( "word" , "{..1}" ) . unwrap( ) , "word" ) ;
831837 }
838+
839+ #[ test]
840+ fn test_empty_list_append_consistency ( ) {
841+ // Create empty list through split of empty string
842+ let result = process ( "" , "{split:,:..|append:!}" ) . unwrap ( ) ;
843+ assert_eq ! ( result, "!" ) ;
844+
845+ // Create empty list through split with no matches
846+ let result = process ( "abc" , "{split:xyz:..|append:!}" ) . unwrap ( ) ;
847+ assert_eq ! ( result, "abc!" ) ;
848+ }
849+
850+ #[ test]
851+ fn test_empty_list_prepend_consistency ( ) {
852+ // Create empty list through split of empty string
853+ let result = process ( "" , "{split:,:..|prepend:!}" ) . unwrap ( ) ;
854+ assert_eq ! ( result, "!" ) ;
855+
856+ // Create empty list through split with no matches
857+ let result = process ( "abc" , "{split:xyz:..|prepend:!}" ) . unwrap ( ) ;
858+ assert_eq ! ( result, "!abc" ) ;
859+ }
860+
861+ #[ test]
862+ fn test_empty_list_vs_other_operations_consistency ( ) {
863+ // Test how other operations handle empty lists for comparison
864+
865+ // Upper on empty list
866+ let upper_result = process ( "" , "{split:,:..|upper}" ) . unwrap ( ) ;
867+ assert_eq ! ( upper_result, "" ) ; // Consistent: empty string
868+
869+ // Lower on empty list
870+ let lower_result = process ( "" , "{split:,:..|lower}" ) . unwrap ( ) ;
871+ assert_eq ! ( lower_result, "" ) ; // Consistent: empty string
872+
873+ // Trim on empty list
874+ let trim_result = process ( "" , "{split:,:..|trim}" ) . unwrap ( ) ;
875+ assert_eq ! ( trim_result, "" ) ; // Consistent: empty string
876+
877+ // Strip on empty list
878+ let strip_result = process ( "" , "{split:,:..|strip:x}" ) . unwrap ( ) ;
879+ assert_eq ! ( strip_result, "" ) ; // Consistent: empty string
880+
881+ // Replace on empty list
882+ let replace_result = process ( "" , "{split:,:..|replace:s/a/b/}" ) . unwrap ( ) ;
883+ assert_eq ! ( replace_result, "" ) ; // Consistent: empty string
884+
885+ // Slice on empty list
886+ let slice_result = process ( "" , "{split:,:..|slice:0..1}" ) . unwrap ( ) ;
887+ assert_eq ! ( slice_result, "" ) ; // Consistent: empty string
888+ }
889+
890+ #[ test]
891+ fn test_empty_list_chain_with_append_prepend ( ) {
892+ // Test chaining operations after append/prepend on empty list
893+
894+ // Chain after append on empty list
895+ let chain_after_append = process ( "" , "{split:,:..|append:!|upper}" ) . unwrap ( ) ;
896+ assert_eq ! ( chain_after_append, "!" ) ;
897+
898+ // Chain after prepend on empty list
899+ let chain_after_prepend = process ( "" , "{split:,:..|prepend:_|lower}" ) . unwrap ( ) ;
900+ assert_eq ! ( chain_after_prepend, "_" ) ;
901+
902+ // But what if we try to split again after append/prepend?
903+ let split_after_append = process ( "" , "{split:,:..|append:a,b|split:,:..|join:-}" ) . unwrap ( ) ;
904+ assert_eq ! ( split_after_append, "a-b" ) ;
905+ }
906+
907+ #[ test]
908+ fn test_empty_list_multiple_appends_prepends ( ) {
909+ // Test multiple append/prepend operations on empty list
910+
911+ let multiple_appends = process ( "" , "{split:,:..|append:!|append:?}" ) . unwrap ( ) ;
912+ assert_eq ! ( multiple_appends, "!?" ) ;
913+
914+ let multiple_prepends = process ( "" , "{split:,:..|prepend:_|prepend:#}" ) . unwrap ( ) ;
915+ assert_eq ! ( multiple_prepends, "#_" ) ;
916+
917+ let mixed = process ( "" , "{split:,:..|append:!|prepend:_}" ) . unwrap ( ) ;
918+ assert_eq ! ( mixed, "_!" ) ;
919+ }
920+
921+ #[ test]
922+ fn test_empty_list_join_behavior ( ) {
923+ // Test join operation on empty list
924+ let join_empty = process ( "" , "{split:,:..|join:-}" ) . unwrap ( ) ;
925+ assert_eq ! ( join_empty, "" ) ; // Should return empty string
926+
927+ // Test join after operations that might create empty list
928+ let join_after_range = process ( "a,b,c" , "{split:,:10..20|join:-}" ) . unwrap ( ) ;
929+ assert_eq ! ( join_after_range, "" ) ; // Should return empty string
930+ }
931+
932+ #[ test]
933+ fn test_expected_consistent_behavior_for_empty_lists ( ) {
934+ // EXPECTED: append/prepend on empty list should maintain list type consistency
935+ // but since it's empty, the final join should use the operation result appropriately
936+ let result1 = process ( "" , "{split:,:..|append:a|append:b}" ) . unwrap ( ) ;
937+ let result2 = process ( "a" , "{append:b}" ) . unwrap ( ) ;
938+ assert_eq ! ( result1, "ab" ) ; // Both should be same
939+ assert_eq ! ( result2, "ab" ) ; // if behavior is consistent
940+ }
941+
942+ #[ test]
943+ fn test_edge_case_empty_string_vs_empty_list ( ) {
944+ // Test difference between empty string and empty list
945+
946+ // Empty string input
947+ let empty_string_append = process ( "" , "{append:!}" ) . unwrap ( ) ;
948+ assert_eq ! ( empty_string_append, "!" ) ; // String operation
949+
950+ // Empty list (from split) append
951+ let empty_list_append = process ( "" , "{split:,:..|append:!}" ) . unwrap ( ) ;
952+ assert_eq ! ( empty_list_append, "!" ) ; // List operation -> String
953+
954+ // These should potentially behave the same way for consistency
955+ assert_eq ! ( empty_string_append, empty_list_append) ;
956+ }
957+
958+ #[ test]
959+ fn test_empty_list_with_different_separators ( ) {
960+ // Test if the separator tracking works correctly with empty lists
961+
962+ let result1 = process ( "" , "{split:,:..|append:a|append:b}" ) . unwrap ( ) ;
963+ assert_eq ! ( result1, "ab" ) ;
964+
965+ let result2 = process ( "" , "{split:-:..|append:a|append:b}" ) . unwrap ( ) ;
966+ assert_eq ! ( result2, "ab" ) ;
967+
968+ // Both should be the same since we're not creating actual lists
969+ assert_eq ! ( result1, result2) ;
970+ }
971+
972+ #[ test]
973+ fn test_empty_list_operation_order_dependency ( ) {
974+ // Test if the order of operations affects empty list handling
975+
976+ // Append then join
977+ let append_then_join = process ( "" , "{split:,:..|append:test|join:-}" ) . unwrap ( ) ;
978+ assert_eq ! ( append_then_join, "test" ) ;
979+
980+ // Join then append (should not be possible, but test error handling)
981+ let join_then_append = process ( "" , "{split:,:..|join:-|append:test}" ) . unwrap ( ) ;
982+ assert_eq ! ( join_then_append, "test" ) ;
983+
984+ // These results expose the internal type conversions
985+ }
986+
987+ #[ test]
988+ fn test_append_prepend_consistent_behavior ( ) {
989+ // All operations on empty lists should return empty results
990+ // except when the operation itself provides content
991+ // All operations should treat empty list as single empty string element:
992+ assert_eq ! ( process( "" , "{split:,:..|upper}" ) . unwrap( ) , "" ) ;
993+ assert_eq ! ( process( "" , "{split:,:..|append:!}" ) . unwrap( ) , "!" ) ;
994+ assert_eq ! ( process( "" , "{split:,:..|prepend:_}" ) . unwrap( ) , "_" ) ;
995+ }
832996}
0 commit comments