mod archive;
mod dir;
mod docx;
mod format;
mod hex;
mod highlight;
mod imgview;
mod keynote;
mod markdown;
mod media;
mod pdf;
mod plain;
mod pptx;
mod query;
mod sheet;
mod svg;
mod text;
mod theme;
mod tui;
mod typeahead;
mod util;
mod video;
mod xlsx;
use format::Format;
use std::io::{self, IsTerminal};
use std::path::Path;
use std::process::ExitCode;
use std::{env, fs};
fn main() -> ExitCode {
let mut plain_flag = false;
let mut path: Option<String> = None;
for arg in env::args().skip(1) {
match arg.as_str() {
"--plain" | "-p" => plain_flag = true,
"-h" | "--help" => {
eprintln!("usage: sucher [--plain] [file|dir]");
return ExitCode::SUCCESS;
}
_ => path = Some(arg),
}
}
let path = path.unwrap_or_else(|| ".".to_string());
let interactive = !plain_flag && io::stdout().is_terminal();
let title = file_title(&path);
match format::classify_path(Path::new(&path)) {
Format::Directory => {
if interactive {
if let Err(e) = dir::run(path) {
eprintln!("sucher: {e}");
return ExitCode::FAILURE;
}
} else {
return emit(&dir::dump(&path));
}
}
Format::Image => {
if interactive {
if let Err(e) = imgview::run(title, path.clone()) {
eprintln!("sucher: {e}");
return ExitCode::FAILURE;
}
} else {
match image::image_dimensions(&path) {
Ok((w, h)) => return emit(&format!("{path}: image {w}×{h}px\n")),
Err(e) => {
eprintln!("sucher: {path}: {e}");
return ExitCode::FAILURE;
}
}
}
}
Format::Sheet => {
if interactive {
if let Err(e) = sheet::run(title, path.clone()) {
eprintln!("sucher: {e}");
return ExitCode::FAILURE;
}
} else {
return emit(&sheet::dump(&path));
}
}
Format::Svg => {
if interactive {
if let Err(e) = svg::run(title, path.clone()) {
eprintln!("sucher: {e}");
return ExitCode::FAILURE;
}
} else {
return emit(&text::dump(&path));
}
}
Format::Pdf => {
if interactive {
if let Err(e) = pdf::run(title, path.clone()) {
eprintln!("sucher: {e}");
return ExitCode::FAILURE;
}
} else {
return emit(&pdf::dump(&path));
}
}
Format::Video => {
if interactive {
if let Err(e) = video::run(title, path.clone()) {
eprintln!("sucher: {e}");
return ExitCode::FAILURE;
}
} else {
return emit(&video::dump(&path));
}
}
Format::Docx => {
let src = match docx::to_markdown(&path) {
Ok(s) => s,
Err(e) => {
eprintln!("sucher: {path}: {e}");
return ExitCode::FAILURE;
}
};
let images = if interactive {
docx::media(&path)
} else {
Vec::new()
};
return render_markdown(interactive, title, src, images);
}
Format::Pptx => {
let src = match pptx::to_markdown(&path) {
Ok(s) => s,
Err(e) => {
eprintln!("sucher: {path}: {e}");
return ExitCode::FAILURE;
}
};
let images = if interactive {
pptx::media(&path)
} else {
Vec::new()
};
return render_markdown(interactive, title, src, images);
}
Format::Keynote => {
if interactive {
if let Err(e) = keynote::run(title, path.clone()) {
eprintln!("sucher: {e}");
return ExitCode::FAILURE;
}
} else {
return emit(&format!("{path}: Keynote presentation\n"));
}
}
Format::Archive => {
if interactive {
if let Err(e) = archive::run(title, path.clone()) {
eprintln!("sucher: {e}");
return ExitCode::FAILURE;
}
} else {
return emit(&archive::dump(&path));
}
}
Format::Binary => {
if interactive {
if let Err(e) = hex::run(title, path.clone()) {
eprintln!("sucher: {e}");
return ExitCode::FAILURE;
}
} else {
return emit(&hex::dump(&path));
}
}
Format::Text => {
if interactive {
if let Err(e) = text::run(title, path.clone()) {
eprintln!("sucher: {e}");
return ExitCode::FAILURE;
}
} else {
return emit(&text::dump(&path));
}
}
Format::Markdown => {
let src = match fs::read_to_string(&path) {
Ok(s) => s,
Err(e) => {
eprintln!("sucher: {path}: {e}");
return ExitCode::FAILURE;
}
};
return render_markdown(interactive, title, src, Vec::new());
}
f @ (Format::Doc | Format::Audio) => {
return unsupported(&path, f, interactive);
}
}
ExitCode::SUCCESS
}
fn unsupported(path: &str, format: Format, interactive: bool) -> ExitCode {
let name = file_title(path);
let meta = metadata_line(path);
if interactive {
eprintln!("sucher: no viewer for {} ({name})", format.label());
eprintln!(" {meta}");
ExitCode::SUCCESS
} else {
emit(&format!("{meta}\n"))
}
}
fn emit(s: &str) -> ExitCode {
use std::io::Write;
let mut out = io::stdout();
match out.write_all(s.as_bytes()).and_then(|()| out.flush()) {
Ok(()) => ExitCode::SUCCESS,
Err(e) if e.kind() == io::ErrorKind::BrokenPipe => ExitCode::SUCCESS,
Err(e) => {
eprintln!("sucher: {e}");
ExitCode::FAILURE
}
}
}
fn metadata_line(path: &str) -> String {
match fs::metadata(path) {
Ok(m) => {
let mut s = util::human_size(m.len());
if let Ok(t) = m.modified() {
s.push_str(&format!(" · {}", util::rel_time(t)));
}
s
}
Err(e) => format!("({e})"),
}
}
fn render_markdown(
interactive: bool,
title: String,
src: String,
images: Vec<std::path::PathBuf>,
) -> ExitCode {
if interactive {
if let Err(e) = tui::run(title, src, images) {
eprintln!("sucher: {e}");
return ExitCode::FAILURE;
}
} else {
return emit(&plain::render(&src));
}
ExitCode::SUCCESS
}
fn file_title(path: &str) -> String {
Path::new(path)
.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_else(|| path.to_string())
}
pub fn open_interactive(path: &str) {
let title = file_title(path);
let format = format::classify_path(Path::new(path));
let r = match format {
Format::Image => imgview::run(title, path.to_string()),
Format::Svg => svg::run(title, path.to_string()),
Format::Sheet => sheet::run(title, path.to_string()),
Format::Pdf => pdf::run(title, path.to_string()),
Format::Video => video::run(title, path.to_string()),
Format::Text => text::run(title, path.to_string()),
Format::Docx => match docx::to_markdown(path) {
Ok(src) => tui::run(title, src, docx::media(path)),
Err(e) => {
eprintln!("sucher: {path}: {e}");
Ok(())
}
},
Format::Pptx => match pptx::to_markdown(path) {
Ok(src) => tui::run(title, src, pptx::media(path)),
Err(e) => {
eprintln!("sucher: {path}: {e}");
Ok(())
}
},
Format::Keynote => keynote::run(title, path.to_string()),
Format::Archive => archive::run(title, path.to_string()),
Format::Binary => hex::run(title, path.to_string()),
Format::Markdown => match fs::read_to_string(path) {
Ok(src) => tui::run(title, src, Vec::new()),
Err(e) => {
eprintln!("sucher: {path}: {e}");
Ok(())
}
},
Format::Directory | Format::Doc | Format::Audio => {
eprintln!("sucher: no viewer for {} ({title})", format.label());
Ok(())
}
};
if let Err(e) = r {
eprintln!("sucher: {e}");
}
}