synd_term/interact/
process.rs

1use std::{
2    io::{self, ErrorKind},
3    path::PathBuf,
4    process::{Command, Stdio},
5};
6
7use itertools::Itertools;
8use url::Url;
9
10use crate::interact::{Interact, OpenBrowserError, OpenEditor, OpenTextBrowser, OpenWebBrowser};
11
12pub struct ProcessInteractor {
13    text_browser: TextBrowserInteractor,
14}
15
16impl ProcessInteractor {
17    pub fn new(text_browser: TextBrowserInteractor) -> Self {
18        Self { text_browser }
19    }
20}
21
22impl OpenWebBrowser for ProcessInteractor {
23    fn open_browser(&self, url: url::Url) -> Result<(), super::OpenBrowserError> {
24        open::that(url.as_str()).map_err(OpenBrowserError::from)
25    }
26}
27
28impl OpenTextBrowser for ProcessInteractor {
29    fn open_text_browser(&self, url: Url) -> Result<(), super::OpenBrowserError> {
30        self.text_browser.open_text_browser(url)
31    }
32}
33
34impl OpenEditor for ProcessInteractor {
35    fn open_editor(&self, initial_content: &str) -> Result<String, super::OpenEditorError> {
36        edit::edit(initial_content).map_err(|err| super::OpenEditorError {
37            message: err.to_string(),
38        })
39    }
40}
41
42impl Interact for ProcessInteractor {}
43
44pub struct TextBrowserInteractor {
45    command: PathBuf,
46    args: Vec<String>,
47}
48
49impl TextBrowserInteractor {
50    pub fn new(command: PathBuf, args: Vec<String>) -> Self {
51        Self { command, args }
52    }
53}
54
55impl OpenTextBrowser for TextBrowserInteractor {
56    #[tracing::instrument(skip(self))]
57    fn open_text_browser(&self, url: Url) -> Result<(), OpenBrowserError> {
58        let status = Command::new(self.command.as_os_str())
59            .args(self.args.iter())
60            .arg(url.as_str())
61            .stdin(Stdio::inherit())
62            .stdout(Stdio::inherit())
63            .stderr(Stdio::inherit())
64            .output()
65            .map_err(|err| {
66                if err.kind() == ErrorKind::NotFound {
67                    OpenBrowserError::CommandNotFound {
68                        command: self.command.clone(),
69                    }
70                } else {
71                    err.into()
72                }
73            })?
74            .status;
75
76        if status.success() {
77            Ok(())
78        } else {
79            let full_command = if self.args.is_empty() {
80                format!("{} {}", self.command.display(), url,)
81            } else {
82                format!(
83                    "{} {} {}",
84                    self.command.display(),
85                    self.args.iter().join(" "),
86                    url,
87                )
88            };
89            Err(io::Error::new(io::ErrorKind::Other, full_command).into())
90        }
91    }
92}