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 {
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)
}
}