Skip to content

Commit bad7d62

Browse files
lalvarezt-bwlalvarezt
authored andcommitted
test(parser): some refactoring and adding more tests
1 parent 48a8d4e commit bad7d62

File tree

2 files changed

+182
-44
lines changed

2 files changed

+182
-44
lines changed

src/pipeline/mod.rs

Lines changed: 181 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,20 @@ pub fn parse_template(template: &str) -> Result<Vec<StringOp>, String> {
4949
}
5050

5151
fn 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

6368
fn 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

147153
pub 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
}

src/pipeline/parser.rs

Lines changed: 1 addition & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use pest::Parser;
22
use pest_derive::Parser;
33

4-
use super::{RangeSpec, StringOp};
4+
use super::{RangeSpec, StringOp, unescape};
55

66
#[derive(Parser)]
77
#[grammar = "pipeline/template.pest"]
@@ -127,29 +127,3 @@ fn parse_range_spec(pair: pest::iterators::Pair<Rule>) -> Result<RangeSpec, Stri
127127
_ => Err(format!("Unknown range spec: {:?}", inner.as_rule())),
128128
}
129129
}
130-
131-
fn unescape(s: &str) -> String {
132-
let mut out = String::new();
133-
let mut chars = s.chars().peekable();
134-
while let Some(c) = chars.next() {
135-
if c == '\\' {
136-
match chars.next() {
137-
Some('|') => out.push('|'),
138-
Some(':') => out.push(':'),
139-
Some('\\') => out.push('\\'),
140-
Some('n') => out.push('\n'),
141-
Some('t') => out.push('\t'),
142-
Some('r') => out.push('\r'),
143-
Some('/') => out.push('/'),
144-
Some(next) => {
145-
out.push('\\');
146-
out.push(next);
147-
}
148-
None => out.push('\\'),
149-
}
150-
} else {
151-
out.push(c);
152-
}
153-
}
154-
out
155-
}

0 commit comments

Comments
 (0)