spawn-editor 0.0.6

Contains utility functions to spawn a text editor
Documentation
#![forbid(unsafe_code)]

use std::borrow::Cow;
use std::convert::AsRef;
use core::fmt;
use std::env::{self, VarError};
use std::process::{Command, ExitStatus};

/// Get the default editor for the current environment
// orignally taken from the crate `default-editor`
pub fn get_editor(override_editor: Option<Cow<str>>) -> Result<Cow<str>, VarError> {
    if let Some(z) = override_editor {
        return Ok(z);
    }

    match env::var("VISUAL") {
        Ok(result) => return Ok(result.into()),
        Err(VarError::NotPresent) => {},
        Err(error) => return Err(error),
    }

    match env::var("EDITOR") {
        Ok(result) => return Ok(result.into()),
        Err(VarError::NotPresent) => {},
        Err(error) => return Err(error),
    }

    Ok("vi".into())
}

#[derive(Debug)]
pub enum SEError {
    Process(std::io::Error),
    Var(VarError),
}

impl std::error::Error for SEError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            SEError::Process(source) => Some(&*source),
            SEError::Var(source) => Some(&*source),
        }
    }
}

impl fmt::Display for SEError {
    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
        match self {
            SEError::Process(_) => {
                formatter.write_str("editor spawning/waiting failed")
            }
            SEError::Var(_) => {
                formatter.write_str("got invalid environment variable")
            }
        }
    }
}

type SEResult = Result<ExitStatus, SEError>;

/// This function either uses the `override_editor` argument as an editor
/// or tries to get this information from the environment variables.
/// A file to edit can be provided via `extra_args`
///
/// Example usage:
/// ```no_run
/// spawn_editor::spawn_editor(Some("nano"), &["src/lib.rs"]);
/// ```
pub fn spawn_editor(override_editor: Option<&str>, extra_args: &[&str]) -> SEResult {
    let editor: std::borrow::Cow<str> = get_editor(override_editor.map(Into::into)).map_err(SEError::Var)?;

    Ok(Command::new(&*editor)
        .args(extra_args)
        .spawn()
        .and_then(|mut c| c.wait())
        .map_err(SEError::Process)?)
}

/// This function is a convenient wrapper around [`spawn_editor`],
/// in case that the arguments aren't simple string slices
pub fn spawn_editor_generic<Ta, Tb>(override_editor: Option<Ta>, extra_args: &[Tb]) -> SEResult
where
    Ta: AsRef<str>,
    Tb: AsRef<str>,
{
    let real_oore = override_editor.as_ref().map(|x| x.as_ref());
    let xar: Vec<_> = extra_args.iter().map(|x| x.as_ref()).collect();
    spawn_editor(real_oore, &xar[..])
}

/// This function is a convenient wrapper around [`spawn_editor_generic`],
/// in case that `override_editor == None`
///
/// Example usage:
/// ```no_run
/// spawn_editor::spawn_editor_with_args(&["src/lib.rs"]);
/// ```
#[inline]
pub fn spawn_editor_with_args<Tb: AsRef<str>>(extra_args: &[Tb]) -> SEResult {
    spawn_editor_generic::<&str, Tb>(None, extra_args)
}

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

    // tests taken from `default-editor v0.1.0`
    mod default_editor {
        use std::env;

        fn it_falls_back_to_vi() {
            env::remove_var("VISUAL");
            env::remove_var("EDITOR");

            assert_eq!(crate::get_editor(None), Ok("vi".into()));
        }

        fn it_returns_visual() {
            env::set_var("VISUAL", "test1");
            env::remove_var("EDITOR");

            assert_eq!(crate::get_editor(None), Ok("test1".to_string().into()));
        }

        fn it_returns_editor() {
            env::remove_var("VISUAL");
            env::set_var("EDITOR", "test2");

            assert_eq!(crate::get_editor(None), Ok("test2".to_string().into()));
        }

        fn it_returns_visual_before_editor() {
            env::set_var("VISUAL", "test3");
            env::set_var("EDITOR", "test4");

            assert_eq!(crate::get_editor(None), Ok("test3".to_string().into()));
        }

        #[test]
        fn all_tests() {
            // Wrap all tests in another function since they cannot be run in parallel
            it_falls_back_to_vi();
            it_returns_visual();
            it_returns_editor();
            it_returns_visual_before_editor();
        }
    }

    #[test]
    #[ignore]
    fn testit() {
        let _ = spawn_editor_with_args(&["src/lib.rs"]);
    }
}