fif/src/formats.rs

161 lines
4.0 KiB
Rust

//! The various formats that [fif](crate) can output to.
use std::io::{self, Write};
#[cfg(unix)]
use std::os::unix::ffi::OsStrExt;
use std::path::Path;
use snailquote::escape;
use crate::scan_error::ScanError;
use crate::{Findings, BACKEND};
/// The current version of fif, as defined in Cargo.toml.
const VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION");
#[doc(hidden)]
type Entries<'a> = [Result<Findings, ScanError<'a>>];
enum Writable<'a> {
String(&'a str),
Path(&'a Path),
Space,
Newline,
}
// the lifetime of a lifetime
impl<'a> From<&'a str> for Writable<'a> {
fn from(s: &'a str) -> Writable<'a> {
Writable::String(s)
}
}
impl<'a> From<&'a Path> for Writable<'a> {
fn from(p: &'a Path) -> Writable<'a> {
Writable::Path(p)
}
}
fn smart_write<W: Write>(f: &mut W, writeables: &[Writable]) -> io::Result<()> {
// ehhhh
for writeable in writeables {
match writeable {
Writable::Space => write!(f, " ")?,
Writable::Newline => writeln!(f,)?,
Writable::String(s) => write!(f, "{}", s)?,
Writable::Path(path) => {
if let Some(string) = path.to_str() {
write!(f, "{}", escape(string))?
} else {
write!(f, "'''")?;
#[cfg(unix)]
f.write_all(&*path.as_os_str().as_bytes())?;
#[cfg(windows)]
write!(f, "{}", path.as_os_str().to_string_lossy())?; // TODO: implement bonked strings for windows
// f.write_all(&*path.as_os_str().encode_wide().collect::<Vec<u16>>())?;
write!(f, "'''")?
}
}
}
}
Ok(())
}
pub trait Format {
fn new() -> Self;
fn rename<W: Write>(&self, f: &mut W, from: &Path, to: &Path) -> io::Result<()>;
fn no_known_extension<W: Write>(&self, f: &mut W, path: &Path) -> io::Result<()>;
fn unreadable<W: Write>(&self, f: &mut W, path: &Path) -> io::Result<()>;
fn unknown_type<W: Write>(&self, f: &mut W, path: &Path) -> io::Result<()>;
fn header<W: Write>(&self, entries: &Entries, f: &mut W) -> io::Result<()>;
fn footer<W: Write>(&self, entries: &Entries, f: &mut W) -> io::Result<()>;
fn write_all<W: Write>(&self, entries: &Entries, f: &mut W) -> io::Result<()> {
// TODO: clean this up - it's kinda messy
self.header(entries, f)?;
for entry in entries {
match entry {
Ok(finding) => {
if let Some(ext) = finding.recommended_extension() {
self.rename(f, &finding.file, &finding.file.with_extension(ext.as_str()))?
} else {
self.no_known_extension(f, &finding.file)?
}
}
Err(error) => {
// something went wrong 0uo
match error {
// failed to read the file
ScanError::File(path) => self.unreadable(f, path)?,
// file was read successfully, but we couldn't determine a mimetype
ScanError::Mime(path) => self.unknown_type(f, path)?,
}
}
}
}
self.footer(entries, f)
}
}
// TODO: maybe make a batch script version for windows
/// Bourne-Shell compatible script.
pub struct Script {}
impl Format for Script {
fn new() -> Self {
Self {}
}
fn rename<W: Write>(&self, f: &mut W, from: &Path, to: &Path) -> io::Result<()> {
smart_write(
f,
&[
"mv -v -i -- ".into(),
from.into(),
Writable::Space,
to.into(),
Writable::Newline,
],
)
}
fn no_known_extension<W: Write>(&self, f: &mut W, path: &Path) -> io::Result<()> {
smart_write(
f,
&["echo No known extension for ".into(), path.into(), Writable::Newline],
)
}
fn unreadable<W: Write>(&self, f: &mut W, path: &Path) -> io::Result<()> {
smart_write(f, &["# Failed to read ".into(), path.into(), Writable::Newline])
}
fn unknown_type<W: Write>(&self, f: &mut W, path: &Path) -> io::Result<()> {
smart_write(
f,
&[
"# Failed to detect mime type for ".into(),
path.into(),
Writable::Newline,
],
)
}
fn header<W: Write>(&self, _: &Entries, f: &mut W) -> io::Result<()> {
writeln!(
f,
"#!/usr/bin/env sh\n# Generated by fif {} ({} backend)",
VERSION.unwrap_or("???"),
BACKEND
)?;
writeln!(f, "\nset -e\n")
}
fn footer<W: Write>(&self, _: &Entries, f: &mut W) -> io::Result<()> {
writeln!(f, "\necho 'Done.'")
}
}