1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
use {EXIT_ERROR, EXIT_SUCCESS, ExitStatus, POLLED_TWICE, Spawn};
use clap::{App, AppSettings, Arg};
use env::{ArgumentsEnvironment, ReportErrorEnvironment, ShiftArgumentsEnvironment, StringWrapper};
use future::{Async, EnvFuture, Poll};
use std::borrow::Cow;
use std::error::Error;
use std::fmt;
use void::Void;

const NUMERIC_ARGUMENT_REQUIRED: &str = "numeric argument required";

#[derive(Debug)]
struct NumericArgumentRequiredError;

impl Error for NumericArgumentRequiredError {
    fn description(&self) -> &str {
        NUMERIC_ARGUMENT_REQUIRED
    }
}

impl fmt::Display for NumericArgumentRequiredError {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
        write!(fmt, "{}", self.description())
    }
}

/// Represents a `shift` builtin command.
///
/// The `shift` builtin command will shift all shell or function positional
/// arguments up by the specified amount. For example, shifting by 2 will
/// result in `$1` holding the previous value of `$3`, `$2` holding the
/// previous value of `$4`, and so on.
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct Shift<I> {
    args: I,
}

/// Creates a new `shift` builtin command with the provided arguments.
pub fn shift<I>(args: I) -> Shift<I::IntoIter>
    where I: IntoIterator,
{
    Shift {
        args: args.into_iter(),
    }
}

/// A future representing a fully spawned `shift` builtin command.
#[must_use = "futures do nothing unless polled"]
#[derive(Debug)]
pub struct SpawnedShift<I> {
    args: Option<I>,
}

impl<T, I, E: ?Sized> Spawn<E> for Shift<I>
    where T: StringWrapper,
          I: Iterator<Item = T>,
          E: ArgumentsEnvironment + ShiftArgumentsEnvironment + ReportErrorEnvironment,
{
    type EnvFuture = SpawnedShift<I>;
    type Future = ExitStatus;
    type Error = Void;

    fn spawn(self, _env: &E) -> Self::EnvFuture {
        SpawnedShift {
            args: Some(self.args),
        }
    }
}

impl<T, I, E: ?Sized> EnvFuture<E> for SpawnedShift<I>
    where T: StringWrapper,
          I: Iterator<Item = T>,
          E: ArgumentsEnvironment + ShiftArgumentsEnvironment + ReportErrorEnvironment,
{
    type Item = ExitStatus;
    type Error = Void;

    fn poll(&mut self, env: &mut E) -> Poll<Self::Item, Self::Error> {
        const SHIFT: &str = "shift";
        const AMT_ARG_NAME: &str = "n";
        const DEFAULT_SHIFT_AMOUNT: &str = "1";

        let app = App::new(SHIFT)
            .setting(AppSettings::NoBinaryName)
            .setting(AppSettings::DisableVersion)
            .about("Shifts positional parameters such that (n+1)th parameter becomes $1, and so on")
            .arg(Arg::with_name(AMT_ARG_NAME)
                .help("the amount of arguments to shift")
                .long_help("the amount of arguments to shift. Must be non negative and <= to $#")
                .validator(|amt| {
                    amt.parse::<usize>()
                        .map(|_| ())
                        .map_err(|_| NUMERIC_ARGUMENT_REQUIRED.into())
                })
                .default_value(DEFAULT_SHIFT_AMOUNT)
            );

        let app_args = self.args.take()
            .expect(POLLED_TWICE)
            .into_iter()
            .map(StringWrapper::into_owned);

        let matches = try_and_report!(SHIFT, app.get_matches_from_safe(app_args), env);

        let amt_arg = matches.value_of_lossy(AMT_ARG_NAME)
            .unwrap_or(Cow::Borrowed(DEFAULT_SHIFT_AMOUNT))
            .parse()
            .map_err(|_| NumericArgumentRequiredError);

        let amt = try_and_report!(SHIFT, amt_arg, env);

        let ret = if amt > env.args_len() {
            EXIT_ERROR
        } else {
            env.shift_args(amt);
            EXIT_SUCCESS
        };

        Ok(Async::Ready(ret))
    }

    fn cancel(&mut self, _env: &mut E) {
        self.args.take();
    }
}