use std::{
env, error, fmt, fs, io,
path::{Path, PathBuf},
process,
string::FromUtf8Error,
};
use toodoux::config::Config;
#[derive(Debug)]
pub enum InteractiveEditingError {
FileError(io::Error),
MissingInteractiveEditor,
InteractiveEditorError(PathBuf, io::Error),
Utf8Error(FromUtf8Error),
}
impl fmt::Display for InteractiveEditingError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
InteractiveEditingError::FileError(ref err) => write!(f, "unable to open file: {}", err),
InteractiveEditingError::MissingInteractiveEditor => f.write_str(
"no interactive editor was found; consider configuring either $EDITOR or the configuration",
),
InteractiveEditingError::InteractiveEditorError(ref path, ref err) => {
write!(
f,
"interactive editor error at path {}: {}",
path.display(),
err
)
}
InteractiveEditingError::Utf8Error(ref err) => {
write!(f, "error while decoding UTF-8: {}", err)
}
}
}
}
impl error::Error for InteractiveEditingError {}
impl From<io::Error> for InteractiveEditingError {
fn from(err: io::Error) -> Self {
Self::FileError(err)
}
}
impl From<FromUtf8Error> for InteractiveEditingError {
fn from(err: FromUtf8Error) -> Self {
Self::Utf8Error(err)
}
}
pub fn interactively_edit(
config: &Config,
file_name: &str,
content: &str,
) -> Result<String, InteractiveEditingError> {
log::debug!("creating temporary directory for interactive session");
let dir = tempdir::TempDir::new("")?;
let file_path = dir.path().join(Path::new(file_name));
log::debug!("creating temporary file {}", file_path.display());
fs::write(&file_path, content)?;
let editor;
if let Ok(env_editor) = env::var("EDITOR") {
if env_editor.is_empty() {
return Err(InteractiveEditingError::MissingInteractiveEditor);
}
log::debug!("editing via $EDITOR ({})", env_editor);
editor = env_editor;
} else if let Some(conf_editor) = config.interactive_editor() {
if conf_editor.is_empty() {
return Err(InteractiveEditingError::MissingInteractiveEditor);
}
log::debug!("editing via configuration editor ({})", conf_editor);
editor = conf_editor.to_owned();
} else {
log::error!("cannot find a suitable interactive editor");
return Err(InteractiveEditingError::MissingInteractiveEditor);
}
let _ = process::Command::new(editor)
.arg(&file_path)
.arg("+$")
.spawn()
.map_err(|e| InteractiveEditingError::InteractiveEditorError(file_path.clone(), e))?
.wait()
.map_err(|e| InteractiveEditingError::InteractiveEditorError(file_path.clone(), e))?;
let content = fs::read_to_string(file_path)?;
Ok(content)
}