conch-runtime-pshaw 0.1.6

A library for evaluating/executing programs written in the shell programming language.
Documentation
use crate::env::{StringWrapper, VariableEnvironment};
use crate::eval::{Fields, TildeExpansion, WordEval, WordEvalConfig};
use futures_core::future::BoxFuture;

/// Evaluates a list of words as if they were double quoted.
///
/// All words retain any special meaning/behavior they may have, except
/// no tilde expansions will be made, and no fields will be split further.
pub async fn double_quoted<W, I, E>(
    words: I,
    env: &mut E,
) -> Result<BoxFuture<'static, Fields<W::EvalResult>>, W::Error>
where
    I: IntoIterator<Item = W>,
    W: WordEval<E>,
    W::EvalResult: 'static + Send,
    E: ?Sized + VariableEnvironment<Var = W::EvalResult>,
    E::VarName: std::borrow::Borrow<String>,
{
    do_double_quoted(words.into_iter(), env).await
}

async fn do_double_quoted<W, I, E>(
    words: I,
    env: &mut E,
) -> Result<BoxFuture<'static, Fields<W::EvalResult>>, W::Error>
where
    I: Iterator<Item = W>,
    W: WordEval<E>,
    W::EvalResult: 'static + Send,
    E: ?Sized + VariableEnvironment<Var = W::EvalResult>,
    E::VarName: std::borrow::Borrow<String>,
{
    // Make sure we are NOT doing any tilde expanions for further field splitting
    let cfg = WordEvalConfig {
        tilde_expansion: TildeExpansion::None,
        split_fields_further: false,
    };

    let mut all_fields = Vec::new();
    let mut cur_field: Option<String> = None;

    macro_rules! append {
        ($val:expr) => {{
            let val = $val;
            match cur_field {
                Some(ref mut cur) => cur.push_str(val.as_str()),
                None => cur_field = Some(val.into_owned()),
            };
        }};
    }

    for w in words {
        match w.eval_with_config(env, cfg).await?.await {
            Fields::Zero => {}
            Fields::Single(s) => append!(s),

            // Since we should have indicated we do NOT want field splitting,
            // we should never encounter a Split variant, however, since we
            // cannot control external implementations, we'll fallback
            // somewhat gracefully rather than panicking.
            f @ Fields::Split(_) | f @ Fields::Star(_) => append!(f.join_with_ifs(env)),

            // According to the POSIX spec, if $@ is empty it should generate NO fields
            // even when within double quotes.
            Fields::At(v) if v.is_empty() => {}

            // Any fields generated by $@ must be maintained, however, the first and last
            // fields of $@ should be concatenated to whatever comes before/after them.
            Fields::At(mut v) => {
                let last = v.pop().unwrap();
                let mut iter = v.into_iter();
                match iter.next() {
                    // v only had one element
                    None => append!(last),

                    Some(first) => {
                        append!(first);
                        all_fields.push(cur_field.take().unwrap().into());
                        all_fields.extend(iter);
                        cur_field = Some(last.into_owned());
                    }
                }
            }
        }
    }

    if let Some(s) = cur_field {
        all_fields.push(s.into());
    }

    let ret = Fields::from(all_fields);

    Ok(Box::pin(async move { ret }))
}