use itertools::{Itertools, Position};
use std::ffi::{OsStr, OsString};
#[cfg(not(target_os = "windows"))]
use std::os::unix::ffi::{OsStrExt, OsStringExt};
pub struct StringSplicer<'a, T: Bytes> {
string: Vec<&'a OsStr>,
to: &'a [T],
}
impl<'a, T: Bytes> StringSplicer<'a, T> {
fn new(string: Vec<&'a OsStr>, to: &'a [T]) -> Self {
Self {
string,
to
}
}
pub fn assemble(self) -> OsString
{
self.assemble_with_sep_and_wrap("", "")
}
pub fn assemble_with_sep(self, sep: impl Bytes) -> OsString
{
self.assemble_with_sep_and_wrap(sep, "")
}
pub fn assemble_with_wrap(self, wrap: impl Bytes) -> OsString
{
self.assemble_with_sep_and_wrap("", wrap)
}
pub fn assemble_with_sep_and_wrap(self,
sep: impl Bytes,
wrap: impl Bytes) -> OsString
{
if self.string.len() == 0 {
return OsString::new();
}
if self.string.len() == 1 {
return self.string[0].to_os_string();
}
let sep = sep.as_byte_slice();
let wrap = wrap.as_byte_slice();
let part_count = self.string.len();
let to_count = self.to.len();
let splice_items = self.to.into_iter()
.enumerate()
.fold(OsString::new(), |mut splice_items, (i, item)| {
splice_items.push(wrap.bytes_as_os_str());
splice_items.push(item.bytes_as_os_str());
splice_items.push(wrap.bytes_as_os_str());
if i+1 < to_count {
splice_items.push(sep.bytes_as_os_str());
}
splice_items
});
self.string
.into_iter()
.take(part_count-1)
.fold(OsString::new(), |mut parts, part| {
parts.push(part);
parts.push(&splice_items);
parts
})
}
}
pub trait Bytes: std::fmt::Debug
{
fn as_byte_slice(&self) -> &[u8];
fn bytes_as_os_str(&self) -> &OsStr {
let bytes = self.as_byte_slice();
OsStr::from_bytes(bytes)
}
}
impl Bytes for &[u8] {
fn as_byte_slice(&self) -> &[u8] {
self
}
}
impl Bytes for Vec<u8> {
fn as_byte_slice(&self) -> &[u8] {
self.as_slice()
}
}
impl Bytes for &Vec<u8> {
fn as_byte_slice(&self) -> &[u8] {
self.as_slice()
}
}
impl Bytes for OsStr {
fn as_byte_slice(&self) -> &[u8] {
self.as_bytes()
}
}
impl Bytes for &OsStr {
fn as_byte_slice(&self) -> &[u8] {
self.as_bytes()
}
}
impl Bytes for &&OsStr {
fn as_byte_slice(&self) -> &[u8] {
self.as_bytes()
}
}
impl Bytes for OsString {
fn as_byte_slice(&self) -> &[u8] {
self.as_os_str().as_byte_slice()
}
}
impl Bytes for &OsString {
fn as_byte_slice(&self) -> &[u8] {
self.as_os_str().as_byte_slice()
}
}
impl Bytes for str {
fn as_byte_slice(&self) -> &[u8] {
self.as_bytes()
}
}
impl Bytes for &str {
fn as_byte_slice(&self) -> &[u8] {
self.as_bytes()
}
}
pub trait OsStrConcat {
fn concat(self, sep: impl Bytes) -> OsString;
}
impl<I> OsStrConcat for I
where
I: IntoIterator,
I::Item: Bytes + Clone,
{
fn concat(self, sep: impl Bytes) -> OsString {
let sep = sep.as_byte_slice();
let mut string = self.into_iter()
.fold(Vec::new(), |mut string, part| {
string.extend_from_slice(part.as_byte_slice());
string.extend_from_slice(sep);
string
});
string.truncate(string.len() - sep.len());
OsString::from_vec(string.to_vec())
}
}
pub trait OsStringTools {
fn replace(self, pat: impl Bytes, to: impl Bytes) -> OsString;
fn escape_double_quote(self) -> OsString;
fn escape_single_quote(self) -> OsString;
fn trim_start(self, pat: impl Bytes) -> OsString;
fn trim_end(self, pat: impl Bytes) -> OsString;
}
impl OsStringTools for OsString {
#[inline]
fn replace(self, pat: impl Bytes, to: impl Bytes) -> OsString {
let pat = pat.as_byte_slice();
if self.contains(pat) {
self.as_os_str().replace(pat, to)
} else {
self
}
}
#[inline]
fn escape_double_quote(self) -> OsString {
if self.contains("\"") {
self.as_os_str().escape_double_quote()
} else {
self
}
}
#[inline]
fn escape_single_quote(self) -> OsString {
if self.contains("'") {
self.as_os_str().escape_single_quote()
} else {
self
}
}
#[inline]
fn trim_start(self, pat: impl Bytes) -> OsString {
let pat = pat.as_byte_slice();
let skip_n = *self.as_bytes()
.chunks(pat.len())
.enumerate()
.take_while(|(_, part)| part == &pat)
.map(|(i,_)| i)
.collect_vec()
.last()
.unwrap_or(&0);
if skip_n == 0 {
return self;
} else {
let mut string = self.into_vec();
string.rotate_left(skip_n+1);
string.truncate((string.len() - skip_n) - 1);
return OsString::from_vec(string);
}
}
#[inline]
fn trim_end(self, pat: impl Bytes) -> OsString {
let pat = pat.as_byte_slice();
let mut string = self.into_vec();
while string.ends_with(pat) {
string.truncate(string.len() - pat.len())
}
OsString::from_vec(string)
}
}
pub trait OsStrTools
{
fn split(&self, pat: impl Bytes) -> Vec<&OsStr>;
fn split_lines(&self) -> Vec<&OsStr>;
fn replace(&self, pat: impl Bytes, to: impl Bytes) -> OsString;
fn trim_start(&self, pat: impl Bytes) -> &OsStr;
fn trim_end(&self, pat: impl Bytes) -> &OsStr;
fn contains(&self, pat: impl Bytes) -> bool;
fn position(&self, pat: impl Bytes) -> Option<usize>;
fn splice<'a, B: Bytes>(&'a self,
from: impl Bytes,
to: &'a [B]) -> StringSplicer<'a, B>;
fn quote_double(&self) -> OsString;
fn quote_single(&self) -> OsString;
fn escape_double_quote(&self) -> OsString;
fn escape_single_quote(&self) -> OsString;
}
impl OsStrTools for OsStr
{
fn split(&self, pat: impl Bytes) -> Vec<&OsStr>
{
let orig_string = self.as_byte_slice();
let pat = pat.as_byte_slice();
let pat_len = pat.len();
let mut last_pos = 0;
let split_string = orig_string
.windows(pat_len)
.enumerate()
.scan(0, |prev_split_end, (i, chars)| {
if chars == pat {
let split_at = *prev_split_end;
*prev_split_end = i+pat_len;
Some(Some((split_at, i)))
} else {
Some(None)
}
})
.filter_map(|s| s)
.map(|(start, end)| {
let split = &orig_string[start..end];
(OsStr::from_bytes(split), end)
})
.with_position()
.batching(move |it| {
match it.next() {
Some(item) => {
let string = match item {
Position::First((string, _)) |
Position::Middle((string, _)) => string,
Position::Last((string, pos)) |
Position::Only((string, pos)) => {
last_pos = pos ;
string
}
};
return Some(string)
}
None => {
if last_pos == 0 {
last_pos = self.len();
if self.as_byte_slice() == pat.as_byte_slice() {
return Some(OsStr::new(""));
} else if self.len() == 0 {
return None;
} else {
return Some(self);
}
} else if last_pos+pat_len <= self.len() {
let last = last_pos;
let remaining = self.len() - last_pos;
last_pos = self.len();
let split = &orig_string[last+pat_len..last+remaining];
return Some(OsStr::from_bytes(split));
} else { return None; }
}
}
})
.collect();
split_string
}
fn replace(&self, pat: impl Bytes, with: impl Bytes) -> OsString
{
if self.len() == 0 {
return OsString::new()
}
let string_len = self.len();
let replace_len = with.as_byte_slice().len();
let replace_iter = std::iter::repeat(OsStr::from_bytes(with.as_byte_slice()));
let splits = self.split(pat);
let split_len = splits.len();
let splits = splits
.into_iter()
.interleave_shortest(replace_iter);
let mut new_string = OsString::with_capacity(string_len + replace_len);
for split in splits.take(split_len*2-1) {
new_string.push(split);
}
new_string
}
fn split_lines(&self) -> Vec<&OsStr> {
let newline = "\n";
self.split(newline)
}
fn quote_double(&self) -> OsString {
let string_len = self.len();
let quote = "\"";
let mut new_string = OsString::with_capacity(string_len + 2);
new_string.push(quote);
new_string.push(self);
new_string.push(quote);
new_string
}
fn quote_single(&self) -> OsString
{
let string = self;
let string_len = string.len();
let quote = OsStr::new("\'");
let mut new_string = OsString::with_capacity(string_len + 2);
new_string.push(quote);
new_string.push(string);
new_string.push(quote);
new_string
}
fn splice<'a, B: Bytes>(&'a self,
pat: impl Bytes,
to: &'a [B]) -> StringSplicer<'a, B> {
let string = self.split(pat);
StringSplicer::new(string, to)
}
fn trim_start(&self, pat: impl Bytes) -> &OsStr {
let mut string = self.as_bytes();
let pat = pat.as_byte_slice();
let pat_len = pat.len();
while string.starts_with(pat) {
string = &string[pat_len..];
}
OsStr::from_bytes(string)
}
fn trim_end(&self, pat: impl Bytes) -> &OsStr {
let mut string = self.as_bytes();
let pat = pat.as_byte_slice();
let pat_len = pat.len();
while string.ends_with(pat) {
string = &string[..string.len()-pat_len];
}
OsStr::from_bytes(string)
}
fn contains(&self, pat: impl Bytes) -> bool {
self.position(pat).is_some()
}
fn position(&self, pat: impl Bytes) -> Option<usize> {
let string = self.as_bytes();
let pat = pat.as_byte_slice();
let pat_len = pat.len();
string.windows(pat_len)
.position(|chars| chars == pat)
}
fn escape_double_quote(&self) -> OsString {
let quote = "\"";
let quote_escaped = "\\\"";
self.replace(quote, quote_escaped)
}
fn escape_single_quote(&self) -> OsString {
let single_quote = "'";
let single_quote_escaped = "'\\''";
self.replace(single_quote, single_quote_escaped)
}
}
#[cfg(target_os = "windows")]
trait WinOsString {
fn from_vec(s: Vec<u8>) -> OsString;
fn into_vec(self) -> Vec<u8>;
}
#[cfg(target_os = "windows")]
impl WinOsString for OsString {
fn from_vec(s: Vec<u8>) -> OsString {
use os_str_bytes::OsStringBytes;
OsStringBytes::from_vec(s.clone())
.expect("INVALID STRING: Tried to convert invalid WTF8 to OsString!")
}
fn into_vec(self) -> Vec<u8> {
use os_str_bytes::OsStringBytes;
OsStringBytes::into_vec(self)
}
}
#[cfg(target_os = "windows")]
trait WinOsStr {
fn from_bytes(bytes: &[u8]) -> &OsStr;
fn as_bytes(&self) -> &[u8];
}
#[cfg(target_os = "windows")]
impl WinOsStr for OsStr {
fn from_bytes(bytes: &[u8]) -> &OsStr {
use os_str_bytes::OsStrBytes;
let _str: std::borrow::Cow<'_, OsStr> = OsStrBytes::from_bytes(bytes)
.expect("INVALID STRING: Tried to convert invalid WTF8 to OsStr!");
unsafe { std::mem::transmute(bytes) }
}
fn as_bytes(&self) -> &[u8] {
unsafe { std::mem::transmute(self) }
}
}
#[test]
fn testsplit() {
let s = OsStr::new("a,d,g");
assert_eq!(s.split(","), vec!["a", "d", "g"]);
let s = OsStr::new("abcd defe geh");
assert_eq!(s.split(" "), vec!["abcd", "defe", "geh"]);
let s = OsStr::new("abcd $s");
assert_eq!(s.split("$s"), vec!["abcd ", ""]);
let s = OsStr::new("abcd $s$s");
assert_eq!(s.split("$s"), vec!["abcd ", "", ""]);
let s = OsStr::new("");
assert_eq!(s.split("$s"), Vec::<&OsStr>::new());
let s = OsStr::new("$s");
assert_eq!(s.split(",,"), &["$s"]);
}
#[test]
fn testreplace() {
let s = OsStr::new("a,d,g");
assert_eq!(s.replace(",", "WUT"), "aWUTdWUTg");
let s = OsStr::new("a,d,g");
assert_eq!(s.replace(".", "WUT"), "a,d,g");
let s = OsStr::new("a,,d,,g");
assert_eq!(s.replace(",,", "WUT"), "aWUTdWUTg");
let s = OsStr::new("");
assert_eq!(s.replace(",,", "WUT"), "");
let s = OsStr::new("$s");
assert_eq!(s.replace(",,", "WUT"), "$s");
let s = OsString::from("a,d,g");
assert_eq!(s.replace(",", "WUT"), "aWUTdWUTg");
let s = OsString::from("a,d,g");
assert_eq!(s.replace(".", "WUT"), "a,d,g");
let s = OsString::from("a,,d,,g");
assert_eq!(s.replace(",,", "WUT"), "aWUTdWUTg");
let s = OsString::from("");
assert_eq!(s.replace(",,", "WUT"), "");
let s = OsString::from("$s");
assert_eq!(s.replace(",,", "WUT"), "$s");
}
#[test]
fn testtrimstart() {
let s = OsStr::new(",,,abcd,defe,geh,,,,");
assert_eq!(s.trim_start(","), "abcd,defe,geh,,,,");
let s = OsString::from(",,,abcd,defe,geh");
assert_eq!(s.trim_start(","), "abcd,defe,geh");
}
#[test]
fn testtrimend() {
let s = OsStr::new(",,,abcd,defe,geh,,,,");
assert_eq!(s.trim_end(","), ",,,abcd,defe,geh");
let s = OsStr::new(",,,abcd,defe,geh,,,,");
assert_eq!(s.trim_end(","), ",,,abcd,defe,geh");
let s = OsStr::new(",,,abcd");
assert_eq!(s.trim_end(","), ",,,abcd");
let s = OsStr::new(",,,abcd,,");
assert_eq!(s.trim_end(","), ",,,abcd");
let s = OsStr::new(",,,");
assert_eq!(s.trim_end(","), "");
let s = OsStr::new(",,,");
assert_eq!(s.trim_end(",,"), ",");
let s = OsStr::new(",,,");
assert_eq!(s.trim_end(",,,"), "");
let s = OsStr::new("");
assert_eq!(s.trim_end(",,,"), "");
let s = OsString::from(",,,abcd,defe,geh,,,,");
assert_eq!(s.trim_end(","), ",,,abcd,defe,geh");
let s = OsString::from(",,,abcd,defe,geh,,,,");
assert_eq!(s.trim_end(","), ",,,abcd,defe,geh");
let s = OsString::from(",,,abcd");
assert_eq!(s.trim_end(","), ",,,abcd");
let s = OsString::from(",,,abcd,,");
assert_eq!(s.trim_end(","), ",,,abcd");
let s = OsString::from(",,,");
assert_eq!(s.trim_end(","), "");
let s = OsString::from(",,,");
assert_eq!(s.trim_end(",,"), ",");
let s = OsString::from(",,,");
assert_eq!(s.trim_end(",,,"), "");
let s = OsString::from("");
assert_eq!(s.trim_end(",,,"), "");
}
#[test]
fn testescape() {
let s = OsStr::new("te'st");
assert_eq!(s.escape_single_quote(), "te'\\''st");
let s = OsStr::new("'te'st'");
assert_eq!(s.escape_single_quote(), "'\\''te'\\''st'\\''");
let s = OsString::from("te'st");
assert_eq!(s.escape_single_quote(), "te'\\''st");
let s = OsString::from("'te'st'");
assert_eq!(s.escape_single_quote(), "'\\''te'\\''st'\\''");
}
#[test]
fn test_escape_double() {
let s = OsStr::new("te\"st");
assert_eq!(s.escape_double_quote(), "te\\\"st");
let s = OsStr::new("\"te\"st\"");
assert_eq!(s.escape_double_quote(), "\\\"te\\\"st\\\"");
let s = OsString::from("te\"st");
assert_eq!(s.escape_double_quote(), "te\\\"st");
let s = OsString::from("\"te\"st\"");
assert_eq!(s.escape_double_quote(), "\\\"te\\\"st\\\"");
}
#[test]
fn testsplice() {
let s = OsStr::new("ls $s");
assert_eq!(s.splice("$s", &["TEST"]).assemble_with_sep(" "),
"ls TEST");
let s = OsStr::new("ls $s");
assert_eq!(s.splice("$s", &["TEST", "TEST"]).assemble_with_sep(" "),
"ls TEST TEST");
let s = OsStr::new("ls $s$s");
assert_eq!(s.splice("$s", &["TEST", "TEST"]).assemble_with_sep(" "),
"ls TEST TESTTEST TEST");
let s = OsStr::new("");
assert_eq!(s.splice("$s", &["TEST", "TEST"]).assemble_with_sep(" "), "");
let s = OsStr::new("ls $s");
assert_eq!(s.splice("$s", &[""]).assemble_with_wrap("'"), "ls ''");
let s = OsStr::new("ls $s");
assert_eq!(s.splice("$s", &["TEST"]).assemble_with_wrap("'"),
"ls 'TEST'");
let s = OsStr::new("ls $s");
assert_eq!(s.splice("$s", &["TEST", "TEST"]).assemble_with_sep_and_wrap(" ", "'"),
"ls 'TEST' 'TEST'");
let s = OsStr::new("ls $s$s");
assert_eq!(s.splice("$s", &["TEST", "TEST"]).assemble_with_sep_and_wrap(" ", "'"),
"ls 'TEST' 'TEST''TEST' 'TEST'");
}
#[test]
fn testconcat() {
let s = &[OsStr::new("1"), OsStr::new("2"), OsStr::new("3")];
assert_eq!(s.concat(" "), "1 2 3");
assert_eq!(s.concat(""), "123");
assert_eq!(s.concat("' '"), "1' '2' '3");
let s = &[OsStr::new("")];
assert_eq!(s.concat(""), "");
assert_eq!(s.concat("' '"), "");
let s: &[&OsStr] = &[];
assert_eq!(s.concat(""), "");
}