pub mod archive;
pub mod code;
pub mod command;
pub mod gitdiff;
pub mod image;
pub mod kitty;
pub mod markdown;
pub mod math;
pub mod mermaid;
pub mod pdf;
pub mod svg;
pub mod table;
pub mod text;
pub mod video;
pub mod window;
use std::path::{Path, PathBuf};
use crate::config::Rule;
#[derive(Debug, Clone)]
pub enum PreviewKind {
Markdown(PathBuf),
Mermaid(PathBuf),
Image(PathBuf),
Svg(PathBuf),
Video(PathBuf),
Pdf(PathBuf),
Code(PathBuf),
Table { path: PathBuf, delimiter: u8 },
Archive {
path: PathBuf,
kind: archive::ArchiveKind,
},
Text(PathBuf),
GitDiff(PathBuf),
MermaidFence(usize),
Command {
path: PathBuf,
template: String,
render_as: Option<String>,
detached: bool,
},
CanNotPreview { ext: String },
}
impl PreviewKind {
pub fn from_rule(rule: &Rule, path: &Path) -> Self {
let p = path.to_path_buf();
if let Some(builtin) = rule.builtin.as_deref() {
return match builtin {
"markdown" => PreviewKind::Markdown(p),
"mermaid" => PreviewKind::Mermaid(p),
"image" => PreviewKind::Image(p),
"svg" => PreviewKind::Svg(p),
"video" => PreviewKind::Video(p),
"pdf" => PreviewKind::Pdf(p),
"code" => PreviewKind::Code(p),
"csv" => PreviewKind::Table {
path: p,
delimiter: b',',
},
"tsv" => PreviewKind::Table {
path: p,
delimiter: b'\t',
},
"archive" => match archive::ArchiveKind::from_path(path) {
Some(kind) => PreviewKind::Archive { path: p, kind },
None => PreviewKind::can_not_preview(path),
},
"text" => PreviewKind::Text(p),
_ => PreviewKind::can_not_preview(path),
};
}
if let Some(template) = rule.command.as_deref() {
return PreviewKind::Command {
path: p,
template: template.to_string(),
render_as: rule.render_as.clone(),
detached: rule.detached,
};
}
PreviewKind::can_not_preview(path)
}
pub fn can_not_preview(path: &Path) -> Self {
let ext = path
.extension()
.and_then(|e| e.to_str())
.unwrap_or("")
.to_string();
PreviewKind::CanNotPreview { ext }
}
}
pub fn is_previewable(path: &Path) -> bool {
std::fs::metadata(path)
.map(|m| m.is_file())
.unwrap_or(false)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn can_not_preview_captures_extension_or_empty() {
match PreviewKind::can_not_preview(Path::new("/x/foo.xyz")) {
PreviewKind::CanNotPreview { ext } => assert_eq!(ext, "xyz"),
other => panic!("CanNotPreview を期待: {other:?}"),
}
match PreviewKind::can_not_preview(Path::new("/x/Makefile")) {
PreviewKind::CanNotPreview { ext } => assert_eq!(ext, "", "拡張子なしは空"),
other => panic!("CanNotPreview を期待: {other:?}"),
}
}
#[test]
fn is_previewable_accepts_regular_files_and_symlinks_to_them() {
use crate::test_support::unique_tmp;
let dir = unique_tmp("konoma_is_previewable_regular");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let file = dir.join("real.txt");
std::fs::write(&file, b"hello").unwrap();
assert!(is_previewable(&file), "通常ファイルは true");
#[cfg(unix)]
{
let link = dir.join("link.txt");
std::os::unix::fs::symlink(&file, &link).unwrap();
assert!(
is_previewable(&link),
"通常ファイルへのシンボリックリンクは追従して true (既存の挙動を壊さない)"
);
}
std::fs::remove_dir_all(&dir).ok();
}
#[test]
fn is_previewable_rejects_directories_and_missing_paths() {
use crate::test_support::unique_tmp;
let dir = unique_tmp("konoma_is_previewable_dir");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
assert!(!is_previewable(&dir), "ディレクトリは false");
assert!(
!is_previewable(&dir.join("does_not_exist")),
"存在しないパスは false"
);
std::fs::remove_dir_all(&dir).ok();
}
#[cfg(unix)]
#[test]
fn is_previewable_rejects_a_fifo_without_blocking() {
use crate::test_support::unique_tmp;
let dir = unique_tmp("konoma_is_previewable_fifo");
let _ = std::fs::remove_dir_all(&dir);
std::fs::create_dir_all(&dir).unwrap();
let fifo = dir.join("pipe");
let status = std::process::Command::new("mkfifo")
.arg(&fifo)
.status()
.expect("mkfifo コマンドを起動できない");
assert!(status.success(), "mkfifo に失敗");
assert!(!is_previewable(&fifo), "FIFO(通常ファイルでない)は false");
let link = dir.join("link_to_pipe");
std::os::unix::fs::symlink(&fifo, &link).unwrap();
assert!(
!is_previewable(&link),
"FIFO へのシンボリックリンクも追従した先の種別で false"
);
std::fs::remove_dir_all(&dir).ok();
}
}