use std::io::{self, Write}; #[cfg(unix)] use std::os::unix::ffi::OsStrExt; use std::path::PathBuf; use snailquote::escape; use crate::scanerror::ScanError; use crate::Findings; const VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION"); type Entries = [Result]; enum Writable<'a> { String(&'a str), Path(&'a PathBuf), 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 PathBuf> for Writable<'a> { fn from(p: &'a PathBuf) -> Writable<'a> { Writable::Path(p) } } fn smart_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::>())?; write!(f, "'''")? } } } } Ok(()) } pub trait Format { fn new() -> Self; fn rename(&self, f: &mut W, from: &PathBuf, to: &PathBuf) -> io::Result<()>; fn no_known_extension(&self, f: &mut W, path: &PathBuf) -> io::Result<()>; fn unreadable(&self, f: &mut W, path: &PathBuf) -> io::Result<()>; fn unknown_type(&self, f: &mut W, path: &PathBuf) -> io::Result<()>; fn header(&self, entries: &Entries, f: &mut W) -> io::Result<()>; fn footer(&self, entries: &Entries, f: &mut W) -> io::Result<()>; fn write_all(&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.0 { // failed to read the file ScanError::File => self.unreadable(f, &error.1)?, // file was read successfully, but we couldn't determine a mimetype ScanError::Mime => self.unknown_type(f, &error.1)?, } } } } self.footer(entries, f) } } // TODO: maybe make a batch script version for windows pub struct Script {} impl Format for Script { fn new() -> Self { Self {} } fn rename(&self, f: &mut W, from: &PathBuf, to: &PathBuf) -> io::Result<()> { smart_write( f, &[ "mv -v -i -- ".into(), from.into(), Writable::Space, to.into(), Writable::Newline, ], ) } fn no_known_extension(&self, f: &mut W, path: &PathBuf) -> io::Result<()> { smart_write( f, &["echo No known extension for ".into(), path.into(), Writable::Newline], ) } fn unreadable(&self, f: &mut W, path: &PathBuf) -> io::Result<()> { smart_write(f, &["# Failed to read ".into(), path.into(), Writable::Newline]) } fn unknown_type(&self, f: &mut W, path: &PathBuf) -> io::Result<()> { smart_write( f, &[ "# Failed to detect mime type for ".into(), path.into(), Writable::Newline, ], ) } fn header(&self, _: &Entries, f: &mut W) -> io::Result<()> { write!( f, "#!/usr/bin/env sh\n# Generated by fif {}.\n\nset -e\n\n", VERSION.unwrap_or("???") ) } fn footer(&self, _: &Entries, f: &mut W) -> io::Result<()> { writeln!(f, "\necho 'Done.'") } }