aboutsummaryrefslogtreecommitdiff
path: root/mingling_core/src/utils/splitter.rs
diff options
context:
space:
mode:
Diffstat (limited to 'mingling_core/src/utils/splitter.rs')
-rw-r--r--mingling_core/src/utils/splitter.rs178
1 files changed, 178 insertions, 0 deletions
diff --git a/mingling_core/src/utils/splitter.rs b/mingling_core/src/utils/splitter.rs
new file mode 100644
index 0000000..6b2ea11
--- /dev/null
+++ b/mingling_core/src/utils/splitter.rs
@@ -0,0 +1,178 @@
+/// A trait for splitting strings into arguments, respecting quotes and escapes.
+///
+/// # Examples
+///
+/// ```
+/// use mingling_core::utils::ArgumentSplitter;
+///
+/// let args = "echo \"hello world\"".split_args();
+/// assert_eq!(args, vec!["echo", "hello world"]);
+/// ```
+pub trait ArgumentSplitter {
+ /// Splits the input string into a vector of argument strings.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use mingling_core::utils::ArgumentSplitter;
+ ///
+ /// let args = "a 'b c' d".split_args();
+ /// assert_eq!(args, vec!["a", "b c", "d"]);
+ /// ```
+ fn split_args(self) -> Vec<String>;
+}
+
+impl<S: AsRef<str>> ArgumentSplitter for S {
+ /// Splits the string into arguments, respecting single quotes, double
+ /// quotes, and backslash escaping.
+ ///
+ /// # Examples
+ ///
+ /// ```
+ /// use mingling_core::utils::ArgumentSplitter;
+ ///
+ /// let args = r#"cmd --flag "value with spaces""#.split_args();
+ /// assert_eq!(args, vec!["cmd", "--flag", "value with spaces"]);
+ /// ```
+ ///
+ /// Escaped characters are unescaped:
+ ///
+ /// ```
+ /// use mingling_core::utils::ArgumentSplitter;
+ ///
+ /// let args = r#"echo a\ b"#.split_args();
+ /// assert_eq!(args, vec!["echo", "a b"]);
+ /// ```
+ fn split_args(self) -> Vec<String> {
+ split_args(self.as_ref())
+ }
+}
+
+/// Splits a string input into arguments, respecting single quotes, double quotes,
+/// and backslash escaping.
+fn split_args(input: &str) -> Vec<String> {
+ let mut result: Vec<String> = Vec::new();
+ let mut current = String::new();
+ let mut chars = input.chars();
+
+ while let Some(ch) = chars.next() {
+ match ch {
+ '\\' => {
+ // Take the next character literally (if any) and add it to current.
+ if let Some(next) = chars.next() {
+ current.push(next);
+ }
+ // If there's no next character, the backslash is just ignored/lost.
+ }
+ '"' | '\'' => {
+ // Start of a quoted segment.
+ let quote_char = ch;
+ let mut escaped = false;
+ loop {
+ match chars.next() {
+ None => break,
+ Some(c) => {
+ if escaped {
+ current.push(c);
+ escaped = false;
+ } else if c == '\\' {
+ escaped = true;
+ } else if c == quote_char {
+ break;
+ } else {
+ current.push(c);
+ }
+ }
+ }
+ }
+ }
+ ' ' => {
+ if !current.is_empty() {
+ result.push(current.clone());
+ current.clear();
+ }
+ }
+ _ => {
+ current.push(ch);
+ }
+ }
+ }
+
+ if !current.is_empty() {
+ result.push(current);
+ }
+
+ result
+}
+
+#[cfg(test)]
+mod splitter_tests {
+ use crate::utils::splitter::split_args;
+
+ #[test]
+ fn test_split_with_double_quotes() {
+ let input = r#"a "b c" d"#;
+ let result = split_args(input);
+ assert_eq!(result, vec!["a", "b c", "d"]);
+ }
+
+ #[test]
+ fn test_split_with_single_quotes() {
+ let input = "a 'b c' d";
+ let result = split_args(input);
+ assert_eq!(result, vec!["a", "b c", "d"]);
+ }
+
+ #[test]
+ fn test_empty_input() {
+ assert!(split_args("").is_empty());
+ }
+
+ #[test]
+ fn test_no_quotes() {
+ let result = split_args("hello world");
+ assert_eq!(result, vec!["hello", "world"]);
+ }
+
+ #[test]
+ fn test_double_quotes_at_edges() {
+ let result = split_args(r#""hello world" foo"#);
+ assert_eq!(result, vec!["hello world", "foo"]);
+ }
+
+ #[test]
+ fn test_single_quotes_at_edges() {
+ let result = split_args("'hello world' foo");
+ assert_eq!(result, vec!["hello world", "foo"]);
+ }
+
+ #[test]
+ fn test_multiple_double_quoted_parts() {
+ let result = split_args(r#"a "b c" d "e f g""#);
+ assert_eq!(result, vec!["a", "b c", "d", "e f g"]);
+ }
+
+ #[test]
+ fn test_multiple_single_quoted_parts() {
+ let result = split_args("a 'b c' d 'e f g'");
+ assert_eq!(result, vec!["a", "b c", "d", "e f g"]);
+ }
+
+ #[test]
+ fn test_backslash_escaped_space() {
+ let result = split_args("a b\\ c d");
+ assert_eq!(result, vec!["a", "b c", "d"]);
+ }
+
+ #[test]
+ fn test_backslash_escaped_double_quote() {
+ let result = split_args(r#"a b\"c d"#);
+ assert_eq!(result, vec!["a", r#"b"c"#, "d"]);
+ }
+
+ #[test]
+ fn test_backslash_escaped_single_quote() {
+ let result = split_args("a b\\'c d");
+ assert_eq!(result, vec!["a", "b'c", "d"]);
+ }
+}