imdl 0.1.16

📦 A 40' shipping container for the internet
Documentation
use crate::common::*;

const SHELL_FLAG: &str = "shell-flag";

const SHELL_POSITIONAL: &str = "<SHELL>";

const SHELL_HELP: &str = "Print completion script for `SHELL`.";

#[derive(StructOpt)]
#[structopt(
  help_message(consts::HELP_MESSAGE),
  version_message(consts::VERSION_MESSAGE),
  about("Print shell completion scripts to standard output.")
)]
pub(crate) struct Completions {
  #[structopt(
    name = SHELL_FLAG,
    long = "shell",
    short = "s",
    value_name = "SHELL",
    possible_values = Shell::VARIANTS,
    help = SHELL_HELP,
  )]
  shell_flag: Option<Shell>,
  #[structopt(
    name = SHELL_POSITIONAL,
    value_name = "SHELL",
    possible_values = Shell::VARIANTS,
    required_unless = "dir",
    required_unless = SHELL_FLAG,
    conflicts_with = SHELL_FLAG,
    help = SHELL_HELP,
  )]
  shell_positional: Option<Shell>,
  #[structopt(
    long = "dir",
    short = "d",
    value_name = "DIR",
    empty_values = false,
    parse(from_os_str),
    help = "Write completion script to `DIR` with an appropriate filename. If `--shell` is not \
            given, write all completion scripts."
  )]
  dir: Option<PathBuf>,
}

impl Completions {
  pub(crate) fn run(self, env: &mut Env) -> Result<()> {
    if self.shell_flag.is_some() || self.shell_positional.is_some() {
      let shell = xor_args(
        "shell_flag",
        self.shell_flag.as_ref(),
        "shell_positional",
        self.shell_positional.as_ref(),
      )?;

      if let Some(dir) = self.dir {
        Self::write(env, &dir, shell)?;
      } else {
        let script = shell.completion_script()?;
        out!(env, "{}", script)?;
      }
    } else {
      let dir = self
        .dir
        .ok_or_else(|| Error::internal("Expected `--dir` to be set"))?;

      for shell in Shell::iter() {
        Self::write(env, &dir, shell)?;
      }
    }

    Ok(())
  }

  fn write(env: &mut Env, dir: &Path, shell: Shell) -> Result<()> {
    let script = shell.completion_script()?;
    let dst = dir.join(shell.completion_script_filename());
    env.write(dst, script)?;
    Ok(())
  }
}

#[cfg(test)]
mod tests {
  use super::*;

  #[test]
  fn shell_required() {
    let mut env = test_env! {
      args: [
        "completions",
      ],
      tree: {},
    };

    assert_matches!(env.run(), Err(Error::Clap { .. }));
  }

  #[test]
  fn output() {
    let mut env = test_env! {
      args: [
        "completions",
        "--shell",
        "bash",
      ],
      tree: {},
    };

    env.assert_ok();

    assert!(env.out().starts_with("_imdl() {"));
  }

  #[test]
  fn output_positional() {
    let mut env = test_env! {
      args: [
        "completions",
        "bash",
      ],
      tree: {},
    };

    env.assert_ok();

    assert!(env.out().starts_with("_imdl() {"));
  }

  #[test]
  fn single_dir() {
    let mut env = test_env! {
      args: [
        "completions",
        "--shell",
        "bash",
        "--dir",
        ".",
      ],
      tree: {},
    };

    env.assert_ok();

    let script = env.read_to_string("imdl.bash");

    assert!(script.starts_with("_imdl() {"));
  }

  #[test]
  fn single_positional() {
    let mut env = test_env! {
      args: [
        "completions",
        "bash",
        "--dir",
        ".",
      ],
      tree: {},
    };

    env.assert_ok();

    let script = env.read_to_string("imdl.bash");

    assert!(script.starts_with("_imdl() {"));
  }

  #[test]
  fn all_dir() {
    let mut env = test_env! {
      args: [
        "completions",
        "--dir",
        ".",
      ],
      tree: {},
    };

    env.assert_ok();

    let script = env.read_to_string("imdl.bash");
    assert!(script.starts_with("_imdl() {"));

    let script = env.read_to_string("_imdl.ps1");
    assert!(script.starts_with("using namespace"));
  }
}