190 lines
5.3 KiB
Rust
190 lines
5.3 KiB
Rust
use anyhow::{Context, Result, anyhow};
|
|
use colored::Colorize;
|
|
use std::path::PathBuf;
|
|
use std::path::Path;
|
|
use tokio::fs;
|
|
|
|
use crate::WikiConfig;
|
|
use crate::cli::EntryCommands;
|
|
|
|
pub async fn handle(command: EntryCommands, root_dir: PathBuf) -> Result<()> {
|
|
match command {
|
|
EntryCommands::List => list_entries(&root_dir).await,
|
|
EntryCommands::New { name, category } => create_entry(&root_dir, &name, category).await,
|
|
EntryCommands::Remove { name } => remove_entry(&root_dir, &name).await,
|
|
EntryCommands::Inspect { name } => inspect_entry(&root_dir, &name).await,
|
|
}
|
|
}
|
|
|
|
async fn list_entries(root: &PathBuf) -> Result<()> {
|
|
let mut entries = fs::read_dir(root).await?;
|
|
let mut rows = Vec::new();
|
|
|
|
// Scan for .toml files
|
|
while let Some(entry) = entries.next_entry().await? {
|
|
let path = entry.path();
|
|
if path.extension().and_then(|s| s.to_str()) == Some("toml") {
|
|
let name = if let Some(file_stem) = path.file_stem() {
|
|
file_stem.to_string_lossy().to_string()
|
|
} else {
|
|
continue;
|
|
};
|
|
|
|
// Read configuration to check for linked files
|
|
let content = fs::read_to_string(&path).await.unwrap_or_default();
|
|
let (md_status, img_status) = if let Ok(config) = toml::from_str::<WikiConfig>(&content)
|
|
{
|
|
let md = check_file_status(root, config.content_file);
|
|
let img = check_file_status(root, config.image);
|
|
(md, img)
|
|
} else {
|
|
("Invalid Config".to_string(), "Unknown".to_string())
|
|
};
|
|
|
|
rows.push((name, md_status, img_status));
|
|
}
|
|
}
|
|
|
|
rows.sort_by(|a, b| a.0.cmp(&b.0));
|
|
|
|
// Print Table
|
|
println!(
|
|
"{:<25} {:<25} {:<25}",
|
|
"Entry Name", "Markdown File", "Image"
|
|
);
|
|
|
|
for (name, md, img) in rows {
|
|
println!("{:<25} {:<25} {:<25}", name, md, img);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
fn check_file_status(root: &Path, filename: Option<String>) -> String {
|
|
match filename {
|
|
Some(f) => {
|
|
let path = root.join("images").join(&f);
|
|
if path.exists() {
|
|
f.green().to_string()
|
|
} else {
|
|
format!("{} (Missing in images/)", f.red())
|
|
}
|
|
}
|
|
None => "None".to_string(),
|
|
}
|
|
}
|
|
|
|
async fn create_entry(root: &Path, title: &str, category: Option<String>) -> Result<()> {
|
|
let slug: String = title
|
|
.trim()
|
|
.to_lowercase()
|
|
.replace([' ', '_'], "-")
|
|
.chars()
|
|
.filter(|c| c.is_alphanumeric() || *c == '-')
|
|
.collect();
|
|
|
|
if slug.is_empty() {
|
|
return Err(anyhow!("Resulting entry name is empty"));
|
|
}
|
|
|
|
let toml_filename = format!("{}.toml", slug);
|
|
let md_filename = format!("{}.md", slug);
|
|
let img_filename = format!("{}.png", slug);
|
|
|
|
let toml_path = root.join(&toml_filename);
|
|
let md_path = root.join(&md_filename);
|
|
|
|
let images_dir = root.join("images");
|
|
fs::create_dir_all(&images_dir).await?;
|
|
|
|
if toml_path.exists() {
|
|
return Err(anyhow!("Entry '{}' already exists", slug));
|
|
}
|
|
|
|
// Minimal default content
|
|
let toml_content = format!(
|
|
r#"title = "{}"{}
|
|
image = "{}"
|
|
content_file = "{}"
|
|
|
|
[infobox]
|
|
"Type" = "Draft"
|
|
"Status" = "WIP"
|
|
"Created" = "2026"
|
|
"Category" = "General""#,
|
|
title,
|
|
if let Some(cat) = category {
|
|
format!("\ncategory = \"{cat}\"")
|
|
} else {
|
|
String::new()
|
|
},
|
|
img_filename,
|
|
md_filename
|
|
);
|
|
|
|
fs::write(&toml_path, toml_content)
|
|
.await
|
|
.context("Failed to write TOML")?;
|
|
|
|
// Markdown Placeholder
|
|
let md_content = format!("Auto-generated markdown file for \"{}\"...", title);
|
|
fs::write(&md_path, md_content)
|
|
.await
|
|
.context("Failed to write Markdown")?;
|
|
|
|
println!("Created entry '{}'", title);
|
|
println!(" - TOML: {:?}", toml_path);
|
|
println!(" - Markdown: {:?}", md_path);
|
|
println!(" - (Expected image: {:?})", images_dir.join(img_filename));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn remove_entry(root: &Path, name: &str) -> Result<()> {
|
|
let toml_path = root.join(format!("{}.toml", name));
|
|
|
|
if !toml_path.exists() {
|
|
return Err(anyhow!(
|
|
"Entry '{}' not found (looked at {:?})",
|
|
name,
|
|
toml_path
|
|
));
|
|
}
|
|
|
|
let content = fs::read_to_string(&toml_path).await?;
|
|
let config: WikiConfig = toml::from_str(&content).context("Failed to parse TOML")?;
|
|
|
|
// Try to remove linked files
|
|
if let Some(md_file) = config.content_file {
|
|
let p = root.join(&md_file);
|
|
if p.exists() {
|
|
fs::remove_file(&p).await?;
|
|
println!("Removed: {}", md_file);
|
|
}
|
|
}
|
|
|
|
if let Some(img_file) = config.image {
|
|
let p = root.join(&img_file);
|
|
if p.exists() {
|
|
fs::remove_file(&p).await?;
|
|
println!("Removed: images/{}", img_file);
|
|
}
|
|
}
|
|
|
|
fs::remove_file(&toml_path).await?;
|
|
println!("Removed: {}.toml", name);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn inspect_entry(root: &Path, name: &str) -> Result<()> {
|
|
let toml_path = root.join(format!("{}.toml", name));
|
|
if !toml_path.exists() {
|
|
return Err(anyhow!("Entry '{}' not found", name));
|
|
}
|
|
|
|
let content = fs::read_to_string(&toml_path).await?;
|
|
println!("{}", content);
|
|
Ok(())
|
|
}
|