use crate::env::{StringWrapper, VariableEnvironment};
use crate::IFS_DEFAULT;
use std::borrow::Borrow;
use std::vec;
lazy_static::lazy_static! {
static ref IFS: String = String::from("IFS");
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum Fields<T> {
Zero,
Single(T),
At(Vec<T>),
Star(Vec<T>),
Split(Vec<T>),
}
impl<T: StringWrapper> Fields<T> {
pub fn is_null(&self) -> bool {
match *self {
Fields::Zero => true,
Fields::Single(ref s) => s.as_str().is_empty(),
Fields::At(ref v) | Fields::Star(ref v) | Fields::Split(ref v) => {
v.iter().all(|s| s.as_str().is_empty())
}
}
}
pub fn join(self) -> T {
match self {
Fields::Zero => String::new().into(),
Fields::Single(s) => s,
Fields::At(v) | Fields::Star(v) | Fields::Split(v) => v
.iter()
.map(StringWrapper::as_str)
.filter(|s| !s.is_empty())
.collect::<Vec<&str>>()
.join(" ")
.into(),
}
}
pub fn join_with_ifs<E: ?Sized>(self, env: &E) -> T
where
E: VariableEnvironment,
E::VarName: Borrow<String>,
E::Var: Borrow<String>,
{
match self {
Fields::Zero => String::new().into(),
Fields::Single(s) => s,
Fields::At(v) | Fields::Star(v) | Fields::Split(v) => {
let sep = env.var(&IFS).map(|s| s.borrow().as_str()).map_or(" ", |s| {
if s.is_empty() {
""
} else {
&s[0..1]
}
});
v.iter()
.map(StringWrapper::as_str)
.collect::<Vec<_>>()
.join(sep)
.into()
}
}
}
pub fn split<E: ?Sized>(self, env: &E) -> Fields<T>
where
E: VariableEnvironment,
E::VarName: Borrow<String>,
E::Var: Borrow<String>,
{
match self {
Fields::Zero => Fields::Zero,
Fields::Single(f) => split_fields_internal(vec![f], env).into(),
Fields::At(fs) => Fields::At(split_fields_internal(fs, env)),
Fields::Star(fs) => Fields::Star(split_fields_internal(fs, env)),
Fields::Split(fs) => Fields::Split(split_fields_internal(fs, env)),
}
}
}
impl<T> From<Vec<T>> for Fields<T> {
fn from(mut fields: Vec<T>) -> Self {
if fields.is_empty() {
Fields::Zero
} else if fields.len() == 1 {
Fields::Single(fields.pop().unwrap())
} else {
Fields::Split(fields)
}
}
}
impl<T> From<T> for Fields<T> {
fn from(t: T) -> Self {
Fields::Single(t)
}
}
impl<T> IntoIterator for Fields<T> {
type Item = T;
type IntoIter = vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
let vec = match self {
Fields::Zero => vec![],
Fields::Single(s) => vec![s],
Fields::At(v) | Fields::Star(v) | Fields::Split(v) => v,
};
vec.into_iter()
}
}
fn split_fields_internal<T, E: ?Sized>(words: Vec<T>, env: &E) -> Vec<T>
where
T: StringWrapper,
E: VariableEnvironment,
E::VarName: Borrow<String>,
E::Var: Borrow<String>,
{
let ifs = env.var(&IFS).map_or(IFS_DEFAULT, |s| s.borrow().as_str());
if ifs.is_empty() {
return words;
}
let whitespace: Vec<char> = ifs.chars().filter(|c| c.is_whitespace()).collect();
let mut fields = Vec::with_capacity(words.len());
'word: for word in words.iter().map(StringWrapper::as_str) {
if word.is_empty() {
continue;
}
let mut iter = word.chars().enumerate().peekable();
loop {
let start;
loop {
match iter.next() {
None => continue 'word,
Some((idx, c)) => {
if whitespace.contains(&c) {
continue;
} else if ifs.contains(c) {
fields.push(String::new().into());
} else {
start = idx;
break;
}
}
}
}
let end;
loop {
match iter.next() {
None => {
end = None;
break;
}
Some((idx, c)) => {
if ifs.contains(c) {
end = Some(idx);
break;
}
}
}
}
let field = match end {
Some(end) => &word[start..end],
None => &word[start..],
};
fields.push(String::from(field).into());
loop {
match iter.peek() {
Some(&(_, c)) if whitespace.contains(&c) => {
iter.next();
}
Some(_) | None => break,
}
}
}
}
fields.shrink_to_fit();
fields
}