aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md25
-rw-r--r--mingling_core/src/lib.rs3
-rw-r--r--mingling_core/src/program/repl_exec.rs6
-rw-r--r--mingling_core/src/utils.rs3
-rw-r--r--mingling_core/src/utils/splitter.rs (renamed from mingling_core/src/program/repl_exec/splitter.rs)78
5 files changed, 94 insertions, 21 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c836b04..1712b4b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -352,6 +352,31 @@ None
`Default` is still derived (all fields default to empty/`None`).
+13. **[`core:utils`]** Added a new `utils` module to `mingling_core` with the `ArgumentSplitter` trait, providing a reusable implementation of shell-style command-line argument splitting. The trait is implemented for `str` and internally for `String`, and provides a `split_args()` method that splits a string input into a `Vec<String>` of arguments, respecting single quotes, double quotes, and backslash escaping.
+
+ **Trait definition:**
+
+ ```rust
+ pub trait ArgumentSplitter {
+ /// Splits the string into a `Vec<String>` of arguments, respecting
+ /// single quotes, double quotes, and backslash escaping.
+ fn split_args(&self) -> Vec<String>;
+ }
+
+ impl ArgumentSplitter for str { /* ... */ }
+ impl ArgumentSplitter for String { /* ... */ }
+ ```
+
+ **Splitting rules** (identical semantics to the previously private `splitter` module):
+
+ - **Whitespace separation** — Arguments are separated by spaces (`' '`); consecutive spaces collapse and empty tokens are dropped.
+ - **Single/double quotes** — Text inside `'...'` or `"..."` is treated as a single argument; the quote characters are stripped. Within quoted segments, backslash escapes the next character (so `"b c"` produces `b c`, and `"b\"c"` produces `b"c`).
+ - **Backslash escaping** (outside quotes) — A backslash takes the next character literally (e.g., `b\ c` produces `b c`; `b\"c` produces `b"c`); a trailing backslash with no following character is ignored/lost.
+
+ **Integration:** The previously private `split_input` / `split_input_string` functions in `mingling_core::program::repl_exec::splitter` have been removed; `repl_exec` now uses `readline.split_args()` on the input string. The `utils` module is exported as `mingling_core::utils`.
+
+ The `ArgumentSplitter` trait is a public API addition, so downstream code can now reuse the same argument-splitting logic that the REPL uses for parsing input lines.
+
#### **BREAKING CHANGES** (API CHANGES):
1. **[`macros`]** **[BREAKING]** Renamed the `extra_macros` feature to `extras`. All feature-gated macro re-exports in `mingling/src/lib.rs` (and throughout the codebase) have been updated from `#[cfg(feature = "extra_macros")]` to `#[cfg(feature = "extras")]`.
diff --git a/mingling_core/src/lib.rs b/mingling_core/src/lib.rs
index 7048307..641118c 100644
--- a/mingling_core/src/lib.rs
+++ b/mingling_core/src/lib.rs
@@ -110,3 +110,6 @@ pub mod __private {
/// Mingling's convention metadatas, which can be bound to types using `#[metadata]`, to provide identification for types
pub mod metadata;
+
+/// Some common utilities in Mingling, providing a collection of functionality needed by many modules.
+pub mod utils;
diff --git a/mingling_core/src/program/repl_exec.rs b/mingling_core/src/program/repl_exec.rs
index b91f7bc..2db5c5a 100644
--- a/mingling_core/src/program/repl_exec.rs
+++ b/mingling_core/src/program/repl_exec.rs
@@ -7,10 +7,8 @@ use std::io::Write;
#[doc(hidden)]
pub mod res;
-mod splitter;
-
use crate::error::{ProgramInternalExecuteError, ProgramPanic};
-use crate::program::repl_exec::splitter::split_input_string;
+use crate::utils::ArgumentSplitter;
use crate::{Program, ProgramCollect, RenderResult};
use crate::{program::repl_exec::res::ResREPL, this};
@@ -58,7 +56,7 @@ where
line: &mut readline,
});
- let mut args = split_input_string(&readline);
+ let mut args = readline.split_args();
p.run_hook_repl_pre_exec(&crate::hook::HookREPLPreExecInfo { args: &args });
match might_be_async::invoke!(exec_once(p, &mut args)) {
diff --git a/mingling_core/src/utils.rs b/mingling_core/src/utils.rs
new file mode 100644
index 0000000..89fcd8f
--- /dev/null
+++ b/mingling_core/src/utils.rs
@@ -0,0 +1,3 @@
+mod splitter;
+
+pub use splitter::ArgumentSplitter;
diff --git a/mingling_core/src/program/repl_exec/splitter.rs b/mingling_core/src/utils/splitter.rs
index 312ad73..6b2ea11 100644
--- a/mingling_core/src/program/repl_exec/splitter.rs
+++ b/mingling_core/src/utils/splitter.rs
@@ -1,12 +1,56 @@
-// Doc Not Optimize
-/// Wraps `split_input` to work with owned `String` inputs.
-pub fn split_input_string(input: &str) -> Vec<String> {
- split_input(input)
+/// 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.
-pub fn split_input(input: &str) -> Vec<String> {
+fn split_args(input: &str) -> Vec<String> {
let mut result: Vec<String> = Vec::new();
let mut current = String::new();
let mut chars = input.chars();
@@ -63,72 +107,72 @@ pub fn split_input(input: &str) -> Vec<String> {
#[cfg(test)]
mod splitter_tests {
- use crate::program::repl_exec::splitter::split_input;
+ use crate::utils::splitter::split_args;
#[test]
fn test_split_with_double_quotes() {
let input = r#"a "b c" d"#;
- let result = split_input(input);
+ 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_input(input);
+ let result = split_args(input);
assert_eq!(result, vec!["a", "b c", "d"]);
}
#[test]
fn test_empty_input() {
- assert!(split_input("").is_empty());
+ assert!(split_args("").is_empty());
}
#[test]
fn test_no_quotes() {
- let result = split_input("hello world");
+ let result = split_args("hello world");
assert_eq!(result, vec!["hello", "world"]);
}
#[test]
fn test_double_quotes_at_edges() {
- let result = split_input(r#""hello world" foo"#);
+ 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_input("'hello world' foo");
+ let result = split_args("'hello world' foo");
assert_eq!(result, vec!["hello world", "foo"]);
}
#[test]
fn test_multiple_double_quoted_parts() {
- let result = split_input(r#"a "b c" d "e f g""#);
+ 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_input("a 'b c' d 'e f g'");
+ 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_input("a b\\ c d");
+ 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_input(r#"a b\"c d"#);
+ 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_input("a b\\'c d");
+ let result = split_args("a b\\'c d");
assert_eq!(result, vec!["a", "b'c", "d"]);
}
}