synd-term 0.3.2

terminal feed viewer
Documentation
use std::{
    io::{self, ErrorKind},
    path::PathBuf,
    process::{Command, Stdio},
};

use itertools::Itertools;
use url::Url;

use crate::interact::{Interact, OpenBrowserError, OpenEditor, OpenTextBrowser, OpenWebBrowser};

pub struct ProcessInteractor {
    text_browser: TextBrowserInteractor,
}

impl ProcessInteractor {
    pub fn new(text_browser: TextBrowserInteractor) -> Self {
        Self { text_browser }
    }
}

impl OpenWebBrowser for ProcessInteractor {
    fn open_browser(&self, url: url::Url) -> Result<(), super::OpenBrowserError> {
        open::that(url.as_str()).map_err(OpenBrowserError::from)
    }
}

impl OpenTextBrowser for ProcessInteractor {
    fn open_text_browser(&self, url: Url) -> Result<(), super::OpenBrowserError> {
        self.text_browser.open_text_browser(url)
    }
}

impl OpenEditor for ProcessInteractor {
    fn open_editor(&self, initial_content: &str) -> Result<String, super::OpenEditorError> {
        edit::edit(initial_content).map_err(|err| super::OpenEditorError {
            message: err.to_string(),
        })
    }
}

impl Interact for ProcessInteractor {}

pub struct TextBrowserInteractor {
    command: PathBuf,
    args: Vec<String>,
}

impl TextBrowserInteractor {
    pub fn new(command: PathBuf, args: Vec<String>) -> Self {
        Self { command, args }
    }
}

impl OpenTextBrowser for TextBrowserInteractor {
    #[tracing::instrument(skip(self))]
    fn open_text_browser(&self, url: Url) -> Result<(), OpenBrowserError> {
        let status = Command::new(self.command.as_os_str())
            .args(self.args.iter())
            .arg(url.as_str())
            .stdin(Stdio::inherit())
            .stdout(Stdio::inherit())
            .stderr(Stdio::inherit())
            .output()
            .map_err(|err| {
                if err.kind() == ErrorKind::NotFound {
                    OpenBrowserError::CommandNotFound {
                        command: self.command.clone(),
                    }
                } else {
                    err.into()
                }
            })?
            .status;

        if status.success() {
            Ok(())
        } else {
            let full_command = if self.args.is_empty() {
                format!("{} {}", self.command.display(), url,)
            } else {
                format!(
                    "{} {} {}",
                    self.command.display(),
                    self.args.iter().join(" "),
                    url,
                )
            };
            Err(io::Error::new(io::ErrorKind::Other, full_command).into())
        }
    }
}