uf 0.1.5

Minimalistic file opener
Documentation
use {
    anyhow::{Context, anyhow, bail},
    std::{
        fmt::{self, Display, Formatter},
        fs, io,
        path::Path,
        process::Command,
        str,
    },
};

#[derive(Debug)]
pub struct MimeType {
    supertype: String,
    subtype: String,
}

impl MimeType {
    /// Get the MIME type of a file.
    ///
    /// # Errors
    ///
    /// Fails if the MIME type cannot be determined.
    pub fn detect<P: AsRef<Path>>(file_path: P) -> anyhow::Result<Self> {
        if !fs::exists(&file_path).context("Could not determine if file exists")? {
            bail!("No such file");
        }

        let output = Command::new("file")
            .arg("--brief")
            .arg("--dereference")
            .arg("--mime-type")
            .arg(file_path.as_ref())
            .output()
            .map_err(|err| match err.kind() {
                io::ErrorKind::NotFound => {
                    anyhow!("'file' command not found. Ensure that the 'file' program is installed on your system.")
                }
                _ => anyhow::Error::from(err).context("'file' command failed"),
            })?;

        if output.status.success() {
            let mime_type =
                str::from_utf8(&output.stdout).context("'file' output is invalid UTF-8")?;
            let (supertype, subtype) = mime_type
                .trim()
                .split_once('/')
                .with_context(|| format!("'file' output is not a valid MIME type: {mime_type}"))?;
            Ok(Self {
                supertype: supertype.to_string(),
                subtype: subtype.to_string(),
            })
        } else {
            let error = String::from_utf8_lossy(&output.stderr);
            Err(anyhow!("{}", error).context("'file' command failed"))
        }
    }

    pub fn supertype(&self) -> &str {
        &self.supertype
    }

    pub fn subtype(&self) -> &str {
        &self.subtype
    }
}

impl Display for MimeType {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f, "{}/{}", self.supertype, self.subtype)
    }
}