From 61a596f777ff7a2f92ef2c4a1c48d4cec9f1e7cd Mon Sep 17 00:00:00 2001 From: lucaronin Date: Mon, 23 Feb 2026 11:28:11 +0100 Subject: [PATCH] fix: rustfmt formatting --- src-tauri/src/ai_chat.rs | 13 +- src-tauri/src/frontmatter.rs | 168 +++++++++--- src-tauri/src/git.rs | 52 ++-- src-tauri/src/github.rs | 48 +++- src-tauri/src/lib.rs | 31 ++- src-tauri/src/main.rs | 2 +- src-tauri/src/menu.rs | 9 +- src-tauri/src/settings.rs | 54 +++- src-tauri/src/vault.rs | 497 +++++++++++++++++++++++++---------- 9 files changed, 628 insertions(+), 246 deletions(-) diff --git a/src-tauri/src/ai_chat.rs b/src-tauri/src/ai_chat.rs index 8da08a2d..b9a3f83a 100644 --- a/src-tauri/src/ai_chat.rs +++ b/src-tauri/src/ai_chat.rs @@ -49,7 +49,10 @@ fn get_api_key() -> Result { fn build_request(req: &AiChatRequest) -> AnthropicRequest { AnthropicRequest { - model: req.model.clone().unwrap_or_else(|| "claude-3-5-haiku-20241022".to_string()), + model: req + .model + .clone() + .unwrap_or_else(|| "claude-3-5-haiku-20241022".to_string()), max_tokens: req.max_tokens.unwrap_or(4096), messages: req.messages.clone(), system: req.system.clone(), @@ -137,8 +140,12 @@ mod tests { fn test_extract_response_text() { let resp = AnthropicResponse { content: vec![ - ContentBlock { text: Some("Hello ".to_string()) }, - ContentBlock { text: Some("world".to_string()) }, + ContentBlock { + text: Some("Hello ".to_string()), + }, + ContentBlock { + text: Some("world".to_string()), + }, ContentBlock { text: None }, ], model: "test".to_string(), diff --git a/src-tauri/src/frontmatter.rs b/src-tauri/src/frontmatter.rs index 2c1e9c91..f0990d06 100644 --- a/src-tauri/src/frontmatter.rs +++ b/src-tauri/src/frontmatter.rs @@ -54,14 +54,20 @@ impl FrontmatterValue { pub fn to_yaml_value(&self) -> String { match self { FrontmatterValue::String(s) => { - if needs_yaml_quoting(s) { quote_yaml_string(s) } else { s.clone() } + if needs_yaml_quoting(s) { + quote_yaml_string(s) + } else { + s.clone() + } } FrontmatterValue::Number(n) => format_yaml_number(*n), FrontmatterValue::Bool(b) => if *b { "true" } else { "false" }.to_string(), FrontmatterValue::List(items) if items.is_empty() => "[]".to_string(), - FrontmatterValue::List(items) => { - items.iter().map(|item| format_list_item(item)).collect::>().join("\n") - } + FrontmatterValue::List(items) => items + .iter() + .map(|item| format_list_item(item)) + .collect::>() + .join("\n"), FrontmatterValue::Null => "null".to_string(), } } @@ -69,7 +75,8 @@ impl FrontmatterValue { /// Check whether a YAML key needs quoting (contains spaces, special chars, etc.). fn needs_key_quoting(key: &str) -> bool { - key.chars().any(|c| !c.is_ascii_alphanumeric() && c != '_' && c != '-') + key.chars() + .any(|c| !c.is_ascii_alphanumeric() && c != '_' && c != '-') } /// Format a key for YAML output (quote if necessary) @@ -84,21 +91,21 @@ pub fn format_yaml_key(key: &str) -> String { /// Check if a line defines a specific key (handles quoted and unquoted keys) fn line_is_key(line: &str, key: &str) -> bool { let trimmed = line.trim_start(); - + if trimmed.starts_with(key) && trimmed[key.len()..].starts_with(':') { return true; } - + let dq = format!("\"{}\":", key); if trimmed.starts_with(&dq) { return true; } - + let sq = format!("'{}\':", key); if trimmed.starts_with(&sq) { return true; } - + false } @@ -121,7 +128,8 @@ fn is_list_continuation(line: &str) -> bool { /// Split content into frontmatter body and the rest after the closing `---`. /// Returns `(fm_content, rest)` where `fm_content` is between the opening and closing `---`. fn split_frontmatter(content: &str) -> Result<(&str, &str), String> { - let fm_end = content[4..].find("\n---") + let fm_end = content[4..] + .find("\n---") .map(|i| i + 4) .ok_or_else(|| "Malformed frontmatter: no closing ---".to_string())?; Ok((&content[4..fm_end], &content[fm_end + 4..])) @@ -168,7 +176,11 @@ fn apply_field_update(lines: &[&str], key: &str, value: Option<&FrontmatterValue } /// Internal function to update frontmatter content -pub fn update_frontmatter_content(content: &str, key: &str, value: Option) -> Result { +pub fn update_frontmatter_content( + content: &str, + key: &str, + value: Option, +) -> Result { if !content.starts_with("---\n") { return match value { Some(v) => Ok(prepend_new_frontmatter(content, key, &v)), @@ -192,15 +204,14 @@ where if !file_path.exists() { return Err(format!("File does not exist: {}", path)); } - - let content = fs::read_to_string(file_path) - .map_err(|e| format!("Failed to read {}: {}", path, e))?; - + + let content = + fs::read_to_string(file_path).map_err(|e| format!("Failed to read {}: {}", path, e))?; + let updated = transform(&content)?; - - fs::write(file_path, &updated) - .map_err(|e| format!("Failed to write {}: {}", path, e))?; - + + fs::write(file_path, &updated).map_err(|e| format!("Failed to write {}: {}", path, e))?; + Ok(updated) } @@ -211,7 +222,12 @@ mod tests { #[test] fn test_update_frontmatter_string() { let content = "---\nStatus: Draft\n---\n# Test\n"; - let updated = update_frontmatter_content(content, "Status", Some(FrontmatterValue::String("Active".to_string()))).unwrap(); + let updated = update_frontmatter_content( + content, + "Status", + Some(FrontmatterValue::String("Active".to_string())), + ) + .unwrap(); assert!(updated.contains("Status: Active")); assert!(!updated.contains("Status: Draft")); } @@ -219,7 +235,12 @@ mod tests { #[test] fn test_update_frontmatter_add_new_key() { let content = "---\nStatus: Draft\n---\n# Test\n"; - let updated = update_frontmatter_content(content, "Owner", Some(FrontmatterValue::String("Luca".to_string()))).unwrap(); + let updated = update_frontmatter_content( + content, + "Owner", + Some(FrontmatterValue::String("Luca".to_string())), + ) + .unwrap(); assert!(updated.contains("Owner: Luca")); assert!(updated.contains("Status: Draft")); } @@ -227,7 +248,12 @@ mod tests { #[test] fn test_update_frontmatter_quoted_key() { let content = "---\n\"Is A\": Note\n---\n# Test\n"; - let updated = update_frontmatter_content(content, "Is A", Some(FrontmatterValue::String("Project".to_string()))).unwrap(); + let updated = update_frontmatter_content( + content, + "Is A", + Some(FrontmatterValue::String("Project".to_string())), + ) + .unwrap(); assert!(updated.contains("\"Is A\": Project")); assert!(!updated.contains("Note")); } @@ -235,7 +261,15 @@ mod tests { #[test] fn test_update_frontmatter_list() { let content = "---\nStatus: Draft\n---\n# Test\n"; - let updated = update_frontmatter_content(content, "aliases", Some(FrontmatterValue::List(vec!["Alias1".to_string(), "Alias2".to_string()]))).unwrap(); + let updated = update_frontmatter_content( + content, + "aliases", + Some(FrontmatterValue::List(vec![ + "Alias1".to_string(), + "Alias2".to_string(), + ])), + ) + .unwrap(); assert!(updated.contains("aliases:")); assert!(updated.contains(" - \"Alias1\"")); assert!(updated.contains(" - \"Alias2\"")); @@ -244,7 +278,12 @@ mod tests { #[test] fn test_update_frontmatter_replace_list() { let content = "---\naliases:\n - Old1\n - Old2\nStatus: Draft\n---\n# Test\n"; - let updated = update_frontmatter_content(content, "aliases", Some(FrontmatterValue::List(vec!["New1".to_string()]))).unwrap(); + let updated = update_frontmatter_content( + content, + "aliases", + Some(FrontmatterValue::List(vec!["New1".to_string()])), + ) + .unwrap(); assert!(updated.contains(" - \"New1\"")); assert!(!updated.contains("Old1")); assert!(!updated.contains("Old2")); @@ -271,7 +310,12 @@ mod tests { #[test] fn test_update_frontmatter_no_existing() { let content = "# Test\n\nSome content here."; - let updated = update_frontmatter_content(content, "Status", Some(FrontmatterValue::String("Draft".to_string()))).unwrap(); + let updated = update_frontmatter_content( + content, + "Status", + Some(FrontmatterValue::String("Draft".to_string())), + ) + .unwrap(); assert!(updated.starts_with("---\n")); assert!(updated.contains("Status: Draft")); assert!(updated.contains("# Test")); @@ -280,7 +324,9 @@ mod tests { #[test] fn test_update_frontmatter_bool() { let content = "---\nStatus: Draft\n---\n# Test\n"; - let updated = update_frontmatter_content(content, "Reviewed", Some(FrontmatterValue::Bool(true))).unwrap(); + let updated = + update_frontmatter_content(content, "Reviewed", Some(FrontmatterValue::Bool(true))) + .unwrap(); assert!(updated.contains("Reviewed: true")); } @@ -324,19 +370,34 @@ mod tests { #[test] fn test_to_yaml_value_string_needs_quoting_bool_like() { - assert_eq!(FrontmatterValue::String("true".to_string()).to_yaml_value(), "\"true\""); - assert_eq!(FrontmatterValue::String("false".to_string()).to_yaml_value(), "\"false\""); + assert_eq!( + FrontmatterValue::String("true".to_string()).to_yaml_value(), + "\"true\"" + ); + assert_eq!( + FrontmatterValue::String("false".to_string()).to_yaml_value(), + "\"false\"" + ); } #[test] fn test_to_yaml_value_string_needs_quoting_null_like() { - assert_eq!(FrontmatterValue::String("null".to_string()).to_yaml_value(), "\"null\""); + assert_eq!( + FrontmatterValue::String("null".to_string()).to_yaml_value(), + "\"null\"" + ); } #[test] fn test_to_yaml_value_string_needs_quoting_number_like() { - assert_eq!(FrontmatterValue::String("42".to_string()).to_yaml_value(), "\"42\""); - assert_eq!(FrontmatterValue::String("3.14".to_string()).to_yaml_value(), "\"3.14\""); + assert_eq!( + FrontmatterValue::String("42".to_string()).to_yaml_value(), + "\"42\"" + ); + assert_eq!( + FrontmatterValue::String("3.14".to_string()).to_yaml_value(), + "\"3.14\"" + ); } #[test] @@ -379,35 +440,46 @@ mod tests { #[test] fn test_update_frontmatter_number() { let content = "---\nStatus: Draft\n---\n# Test\n"; - let updated = update_frontmatter_content(content, "Priority", Some(FrontmatterValue::Number(5.0))).unwrap(); + let updated = + update_frontmatter_content(content, "Priority", Some(FrontmatterValue::Number(5.0))) + .unwrap(); assert!(updated.contains("Priority: 5")); } #[test] fn test_update_frontmatter_number_float() { let content = "---\nStatus: Draft\n---\n# Test\n"; - let updated = update_frontmatter_content(content, "Score", Some(FrontmatterValue::Number(9.5))).unwrap(); + let updated = + update_frontmatter_content(content, "Score", Some(FrontmatterValue::Number(9.5))) + .unwrap(); assert!(updated.contains("Score: 9.5")); } #[test] fn test_update_frontmatter_null() { let content = "---\nStatus: Draft\n---\n# Test\n"; - let updated = update_frontmatter_content(content, "ClearMe", Some(FrontmatterValue::Null)).unwrap(); + let updated = + update_frontmatter_content(content, "ClearMe", Some(FrontmatterValue::Null)).unwrap(); assert!(updated.contains("ClearMe: null")); } #[test] fn test_update_frontmatter_empty_list() { let content = "---\nStatus: Draft\n---\n# Test\n"; - let updated = update_frontmatter_content(content, "tags", Some(FrontmatterValue::List(vec![]))).unwrap(); + let updated = + update_frontmatter_content(content, "tags", Some(FrontmatterValue::List(vec![]))) + .unwrap(); assert!(updated.contains("tags: []")); } #[test] fn test_update_frontmatter_malformed_no_closing_fence() { let content = "---\nStatus: Draft\nNo closing fence here"; - let result = update_frontmatter_content(content, "Status", Some(FrontmatterValue::String("Active".to_string()))); + let result = update_frontmatter_content( + content, + "Status", + Some(FrontmatterValue::String("Active".to_string())), + ); assert!(result.is_err()); assert!(result.unwrap_err().contains("Malformed frontmatter")); } @@ -472,7 +544,12 @@ mod tests { #[test] fn test_roundtrip_update_string() { let content = "---\nStatus: Draft\n---\n# Test\n"; - let updated = update_frontmatter_content(content, "Status", Some(FrontmatterValue::String("Active".to_string()))).unwrap(); + let updated = update_frontmatter_content( + content, + "Status", + Some(FrontmatterValue::String("Active".to_string())), + ) + .unwrap(); // Parse back with gray_matter let matter = gray_matter::Matter::::new(); let parsed = matter.parse(&updated); @@ -487,7 +564,15 @@ mod tests { #[test] fn test_roundtrip_update_list() { let content = "---\nStatus: Draft\n---\n# Test\n"; - let updated = update_frontmatter_content(content, "aliases", Some(FrontmatterValue::List(vec!["A".to_string(), "B".to_string()]))).unwrap(); + let updated = update_frontmatter_content( + content, + "aliases", + Some(FrontmatterValue::List(vec![ + "A".to_string(), + "B".to_string(), + ])), + ) + .unwrap(); let matter = gray_matter::Matter::::new(); let parsed = matter.parse(&updated); let data = parsed.data.unwrap(); @@ -508,7 +593,12 @@ mod tests { #[test] fn test_roundtrip_add_then_delete() { let content = "---\nStatus: Draft\n---\n# Test\n"; - let with_owner = update_frontmatter_content(content, "Owner", Some(FrontmatterValue::String("Luca".to_string()))).unwrap(); + let with_owner = update_frontmatter_content( + content, + "Owner", + Some(FrontmatterValue::String("Luca".to_string())), + ) + .unwrap(); assert!(with_owner.contains("Owner: Luca")); let without_owner = update_frontmatter_content(&with_owner, "Owner", None).unwrap(); assert!(!without_owner.contains("Owner")); diff --git a/src-tauri/src/git.rs b/src-tauri/src/git.rs index 89e037b4..d7d200c1 100644 --- a/src-tauri/src/git.rs +++ b/src-tauri/src/git.rs @@ -181,8 +181,8 @@ pub fn get_file_diff(vault_path: &str, file_path: &str) -> Result = content.lines().map(|l| format!("+{}", l)).collect(); return Ok(format!( "diff --git a/{0} b/{0}\nnew file\n--- /dev/null\n+++ b/{0}\n@@ -0,0 +1,{1} @@\n{2}", @@ -197,7 +197,11 @@ pub fn get_file_diff(vault_path: &str, file_path: &str) -> Result Result { +pub fn get_file_diff_at_commit( + vault_path: &str, + file_path: &str, + commit_hash: &str, +) -> Result { let vault = Path::new(vault_path); let file = Path::new(file_path); @@ -211,7 +215,13 @@ pub fn get_file_diff_at_commit(vault_path: &str, file_path: &str, commit_hash: & // Show diff between commit^ and commit for this file let output = Command::new("git") - .args(["diff", &format!("{}^", commit_hash), commit_hash, "--", relative_str]) + .args([ + "diff", + &format!("{}^", commit_hash), + commit_hash, + "--", + relative_str, + ]) .current_dir(vault) .output() .map_err(|e| format!("Failed to run git diff: {}", e))?; @@ -356,11 +366,7 @@ mod tests { .output() .unwrap(); - let history = get_file_history( - vault.to_str().unwrap(), - file.to_str().unwrap(), - ) - .unwrap(); + let history = get_file_history(vault.to_str().unwrap(), file.to_str().unwrap()).unwrap(); assert_eq!(history.len(), 2); assert_eq!(history[0].message, "Update test"); @@ -377,11 +383,7 @@ mod tests { let file = vault.join("new.md"); fs::write(&file, "# New\n").unwrap(); - let history = get_file_history( - vault.to_str().unwrap(), - file.to_str().unwrap(), - ) - .unwrap(); + let history = get_file_history(vault.to_str().unwrap(), file.to_str().unwrap()).unwrap(); assert!(history.is_empty()); } @@ -436,11 +438,7 @@ mod tests { fs::write(&file, "# Test\n\nModified content.").unwrap(); - let diff = get_file_diff( - vault.to_str().unwrap(), - file.to_str().unwrap(), - ) - .unwrap(); + let diff = get_file_diff(vault.to_str().unwrap(), file.to_str().unwrap()).unwrap(); assert!(!diff.is_empty()); assert!(diff.contains("-Original content.")); @@ -485,12 +483,8 @@ mod tests { .unwrap(); let hash = String::from_utf8_lossy(&log.stdout).trim().to_string(); - let diff = get_file_diff_at_commit( - vault.to_str().unwrap(), - file.to_str().unwrap(), - &hash, - ) - .unwrap(); + let diff = get_file_diff_at_commit(vault.to_str().unwrap(), file.to_str().unwrap(), &hash) + .unwrap(); assert!(!diff.is_empty()); assert!(diff.contains("-Original content.")); @@ -522,12 +516,8 @@ mod tests { .unwrap(); let hash = String::from_utf8_lossy(&log.stdout).trim().to_string(); - let diff = get_file_diff_at_commit( - vault.to_str().unwrap(), - file.to_str().unwrap(), - &hash, - ) - .unwrap(); + let diff = get_file_diff_at_commit(vault.to_str().unwrap(), file.to_str().unwrap(), &hash) + .unwrap(); assert!(!diff.is_empty()); assert!(diff.contains("+# Initial")); diff --git a/src-tauri/src/github.rs b/src-tauri/src/github.rs index c66938e2..cf5429a3 100644 --- a/src-tauri/src/github.rs +++ b/src-tauri/src/github.rs @@ -125,7 +125,12 @@ pub async fn github_create_repo( pub fn clone_repo(url: &str, token: &str, local_path: &str) -> Result { let dest = Path::new(local_path); - if dest.exists() && dest.read_dir().map(|mut d| d.next().is_some()).unwrap_or(false) { + if dest.exists() + && dest + .read_dir() + .map(|mut d| d.next().is_some()) + .unwrap_or(false) + { return Err(format!( "Destination '{}' already exists and is not empty", local_path @@ -163,7 +168,10 @@ fn inject_token_into_url(url: &str, token: &str) -> Result { // Handle URLs that already have a host Ok(format!("https://oauth2:{}@{}", token, rest)) } else { - Err(format!("Unsupported URL format: {}. Use an HTTPS URL.", url)) + Err(format!( + "Unsupported URL format: {}. Use an HTTPS URL.", + url + )) } } @@ -245,7 +253,11 @@ mod tests { let path = dir.path(); std::fs::write(path.join("existing.txt"), "data").unwrap(); - let result = clone_repo("https://github.com/test/repo.git", "token", path.to_str().unwrap()); + let result = clone_repo( + "https://github.com/test/repo.git", + "token", + path.to_str().unwrap(), + ); assert!(result.is_err()); assert!(result.unwrap_err().contains("not empty")); } @@ -255,7 +267,11 @@ mod tests { let dir = tempfile::TempDir::new().unwrap(); let dest = dir.path().join("new-clone"); - let result = clone_repo("git@github.com:user/repo.git", "token", dest.to_str().unwrap()); + let result = clone_repo( + "git@github.com:user/repo.git", + "token", + dest.to_str().unwrap(), + ); assert!(result.is_err()); assert!(result.unwrap_err().contains("Unsupported URL format")); } @@ -268,7 +284,11 @@ mod tests { std::fs::create_dir(&dest).unwrap(); // This will fail at the git clone step (invalid URL) but should pass the directory check - let result = clone_repo("https://github.com/nonexistent/repo.git", "token", dest.to_str().unwrap()); + let result = clone_repo( + "https://github.com/nonexistent/repo.git", + "token", + dest.to_str().unwrap(), + ); assert!(result.is_err()); // Should fail at git clone, not at directory check assert!(result.unwrap_err().contains("git clone failed")); @@ -280,9 +300,21 @@ mod tests { let path = dir.path(); // Initialize a git repo - StdCommand::new("git").args(["init"]).current_dir(path).output().unwrap(); - StdCommand::new("git").args(["remote", "add", "origin", "https://github.com/user/repo.git"]) - .current_dir(path).output().unwrap(); + StdCommand::new("git") + .args(["init"]) + .current_dir(path) + .output() + .unwrap(); + StdCommand::new("git") + .args([ + "remote", + "add", + "origin", + "https://github.com/user/repo.git", + ]) + .current_dir(path) + .output() + .unwrap(); let result = configure_remote_auth( path.to_str().unwrap(), diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index c0632da2..90a5902c 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -7,11 +7,11 @@ pub mod settings; pub mod vault; use ai_chat::{AiChatRequest, AiChatResponse}; +use frontmatter::FrontmatterValue; use git::{GitCommit, ModifiedFile}; use github::GithubRepo; use settings::Settings; -use vault::{VaultEntry, RenameResult}; -use frontmatter::FrontmatterValue; +use vault::{RenameResult, VaultEntry}; #[tauri::command] fn list_vault(path: String) -> Result, String> { @@ -24,7 +24,11 @@ fn get_note_content(path: String) -> Result { } #[tauri::command] -fn update_frontmatter(path: String, key: String, value: FrontmatterValue) -> Result { +fn update_frontmatter( + path: String, + key: String, + value: FrontmatterValue, +) -> Result { vault::update_frontmatter(&path, &key, value) } @@ -49,7 +53,11 @@ fn get_file_diff(vault_path: String, path: String) -> Result { } #[tauri::command] -fn get_file_diff_at_commit(vault_path: String, path: String, commit_hash: String) -> Result { +fn get_file_diff_at_commit( + vault_path: String, + path: String, + commit_hash: String, +) -> Result { git::get_file_diff_at_commit(&vault_path, &path, &commit_hash) } @@ -74,7 +82,11 @@ fn save_image(vault_path: String, filename: String, data: String) -> Result Result { +fn rename_note( + vault_path: String, + old_path: String, + new_title: String, +) -> Result { vault::rename_note(&vault_path, &old_path, &new_title) } @@ -99,7 +111,11 @@ async fn github_list_repos(token: String) -> Result, String> { } #[tauri::command] -async fn github_create_repo(token: String, name: String, private: bool) -> Result { +async fn github_create_repo( + token: String, + name: String, + private: bool, +) -> Result { github::github_create_repo(&token, &name, private).await } @@ -122,7 +138,8 @@ pub fn run() { #[cfg(desktop)] { - app.handle().plugin(tauri_plugin_updater::Builder::new().build())?; + app.handle() + .plugin(tauri_plugin_updater::Builder::new().build())?; app.handle().plugin(tauri_plugin_process::init())?; menu::setup_menu(app)?; } diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 35e5146b..3e4e0df1 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -2,5 +2,5 @@ #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] fn main() { - laputa_lib::run(); + laputa_lib::run(); } diff --git a/src-tauri/src/menu.rs b/src-tauri/src/menu.rs index 7b3c8ac8..197a7d4c 100644 --- a/src-tauri/src/menu.rs +++ b/src-tauri/src/menu.rs @@ -1,4 +1,7 @@ -use tauri::{menu::{MenuBuilder, MenuItemBuilder, SubmenuBuilder}, App, Emitter}; +use tauri::{ + menu::{MenuBuilder, MenuItemBuilder, SubmenuBuilder}, + App, Emitter, +}; const VIEW_ITEMS: [(&str, &str, &str); 3] = [ ("view-editor-only", "Editor Only", "CmdOrCtrl+1"), @@ -17,9 +20,7 @@ pub fn setup_menu(app: &App) -> Result<(), Box> { } let view_submenu = view_menu.build()?; - let menu = MenuBuilder::new(app) - .item(&view_submenu) - .build()?; + let menu = MenuBuilder::new(app).item(&view_submenu).build()?; app.set_menu(menu)?; diff --git a/src-tauri/src/settings.rs b/src-tauri/src/settings.rs index b12e11d9..6652e6f7 100644 --- a/src-tauri/src/settings.rs +++ b/src-tauri/src/settings.rs @@ -20,10 +20,9 @@ fn get_settings_at(path: &PathBuf) -> Result { if !path.exists() { return Ok(Settings::default()); } - let content = fs::read_to_string(path) - .map_err(|e| format!("Failed to read settings: {}", e))?; - serde_json::from_str(&content) - .map_err(|e| format!("Failed to parse settings: {}", e)) + let content = + fs::read_to_string(path).map_err(|e| format!("Failed to read settings: {}", e))?; + serde_json::from_str(&content).map_err(|e| format!("Failed to parse settings: {}", e)) } fn save_settings_at(path: &PathBuf, settings: Settings) -> Result<(), String> { @@ -34,16 +33,27 @@ fn save_settings_at(path: &PathBuf, settings: Settings) -> Result<(), String> { // Trim whitespace and convert empty strings to None let cleaned = Settings { - anthropic_key: settings.anthropic_key.map(|k| k.trim().to_string()).filter(|k| !k.is_empty()), - openai_key: settings.openai_key.map(|k| k.trim().to_string()).filter(|k| !k.is_empty()), - google_key: settings.google_key.map(|k| k.trim().to_string()).filter(|k| !k.is_empty()), - github_token: settings.github_token.map(|k| k.trim().to_string()).filter(|k| !k.is_empty()), + anthropic_key: settings + .anthropic_key + .map(|k| k.trim().to_string()) + .filter(|k| !k.is_empty()), + openai_key: settings + .openai_key + .map(|k| k.trim().to_string()) + .filter(|k| !k.is_empty()), + google_key: settings + .google_key + .map(|k| k.trim().to_string()) + .filter(|k| !k.is_empty()), + github_token: settings + .github_token + .map(|k| k.trim().to_string()) + .filter(|k| !k.is_empty()), }; let json = serde_json::to_string_pretty(&cleaned) .map_err(|e| format!("Failed to serialize settings: {}", e))?; - fs::write(path, json) - .map_err(|e| format!("Failed to write settings: {}", e)) + fs::write(path, json).map_err(|e| format!("Failed to write settings: {}", e)) } pub fn get_settings() -> Result { @@ -71,7 +81,15 @@ mod tests { let s = Settings::default(); assert_eq!( format!("{:?}", s), - format!("{:?}", Settings { anthropic_key: None, openai_key: None, google_key: None, github_token: None }) + format!( + "{:?}", + Settings { + anthropic_key: None, + openai_key: None, + google_key: None, + github_token: None + } + ) ); } @@ -140,9 +158,19 @@ mod tests { let dir = tempfile::TempDir::new().unwrap(); let path = dir.path().join("nested").join("dir").join("settings.json"); - save_settings_at(&path, Settings { anthropic_key: Some("key".to_string()), ..Default::default() }).unwrap(); + save_settings_at( + &path, + Settings { + anthropic_key: Some("key".to_string()), + ..Default::default() + }, + ) + .unwrap(); assert!(path.exists()); - assert_eq!(get_settings_at(&path).unwrap().anthropic_key.as_deref(), Some("key")); + assert_eq!( + get_settings_at(&path).unwrap().anthropic_key.as_deref(), + Some("key") + ); } #[test] diff --git a/src-tauri/src/vault.rs b/src-tauri/src/vault.rs index 6143506c..a2266de9 100644 --- a/src-tauri/src/vault.rs +++ b/src-tauri/src/vault.rs @@ -8,7 +8,7 @@ use std::path::Path; use std::time::UNIX_EPOCH; use walkdir::WalkDir; -use crate::frontmatter::{with_frontmatter, update_frontmatter_content}; +use crate::frontmatter::{update_frontmatter_content, with_frontmatter}; #[derive(Debug, Serialize, Deserialize, Clone, Default)] pub struct VaultEntry { @@ -178,7 +178,9 @@ fn without_h1_line(s: &str) -> Option<&str> { fn collect_until(chars: &mut impl Iterator, delimiter: char) -> String { let mut buf = String::new(); for c in chars.by_ref() { - if c == delimiter { break; } + if c == delimiter { + break; + } buf.push(c); } buf @@ -187,7 +189,9 @@ fn collect_until(chars: &mut impl Iterator, delimiter: char) -> Str /// Skip all chars until a delimiter (consuming the delimiter). fn skip_until(chars: &mut impl Iterator, delimiter: char) { for c in chars.by_ref() { - if c == delimiter { break; } + if c == delimiter { + break; + } } } @@ -219,19 +223,26 @@ fn strip_markdown_chars(s: &str) -> String { /// Parse frontmatter from raw YAML data extracted by gray_matter. fn parse_frontmatter(data: &HashMap) -> Frontmatter { // Convert HashMap to serde_json::Value for deserialization - let value = serde_json::Value::Object( - data.iter() - .map(|(k, v)| (k.clone(), v.clone())) - .collect(), - ); + let value = + serde_json::Value::Object(data.iter().map(|(k, v)| (k.clone(), v.clone())).collect()); serde_json::from_value(value).unwrap_or_default() } /// Known non-relationship frontmatter keys to skip (case-insensitive comparison). /// Only skip keys that can never contain wikilinks. const SKIP_KEYS: &[&str] = &[ - "is a", "aliases", "status", "cadence", "archived", "trashed", "trashed at", - "created at", "created time", "icon", "color", "order", + "is a", + "aliases", + "status", + "cadence", + "archived", + "trashed", + "trashed at", + "created at", + "created time", + "icon", + "color", + "order", ]; /// Check if a string contains a wikilink pattern `[[...]]`. @@ -243,7 +254,9 @@ fn contains_wikilink(s: &str) -> bool { /// Returns a HashMap where each key is the original frontmatter field name /// and the value is a Vec of wikilink strings found in that field. /// Handles both single string values and arrays of strings. -fn extract_relationships(data: &HashMap) -> HashMap> { +fn extract_relationships( + data: &HashMap, +) -> HashMap> { let mut relationships = HashMap::new(); for (key, value) in data { @@ -296,7 +309,8 @@ fn infer_type_from_folder(folder: &str) -> String { "essay" => "Essay", "evergreen" => "Evergreen", _ => return capitalize_first(folder), - }.to_string() + } + .to_string() } /// Resolve `is_a` from frontmatter, falling back to parent folder inference. @@ -312,28 +326,35 @@ fn resolve_is_a(fm_is_a: Option, path: &Path) -> Option { /// Parse created_at from frontmatter (prefer "Created at" over "Created time"). fn parse_created_at(fm: &Frontmatter) -> Option { - fm.created_at.as_ref().and_then(|s| parse_iso_date(s)) + fm.created_at + .as_ref() + .and_then(|s| parse_iso_date(s)) .or_else(|| fm.created_time.as_ref().and_then(|s| parse_iso_date(s))) } /// Extract frontmatter and relationships from parsed gray_matter data. -fn extract_fm_and_rels(data: Option) -> (Frontmatter, HashMap>) { +fn extract_fm_and_rels( + data: Option, +) -> (Frontmatter, HashMap>) { let hash = match data { Some(gray_matter::Pod::Hash(map)) => map, _ => return (Frontmatter::default(), HashMap::new()), }; - let json_map: HashMap = hash - .into_iter() - .map(|(k, v)| (k, pod_to_json(v))) - .collect(); - (parse_frontmatter(&json_map), extract_relationships(&json_map)) + let json_map: HashMap = + hash.into_iter().map(|(k, v)| (k, pod_to_json(v))).collect(); + ( + parse_frontmatter(&json_map), + extract_relationships(&json_map), + ) } /// Read file metadata (modified_at timestamp, file size). fn read_file_metadata(path: &Path) -> Result<(Option, u64), String> { - let metadata = fs::metadata(path) - .map_err(|e| format!("Failed to stat {}: {}", path.display(), e))?; - let modified_at = metadata.modified().ok() + let metadata = + fs::metadata(path).map_err(|e| format!("Failed to stat {}: {}", path.display(), e))?; + let modified_at = metadata + .modified() + .ok() .and_then(|t| t.duration_since(UNIX_EPOCH).ok()) .map(|d| d.as_secs()); Ok((modified_at, metadata.len())) @@ -343,7 +364,8 @@ fn read_file_metadata(path: &Path) -> Result<(Option, u64), String> { pub fn parse_md_file(path: &Path) -> Result { let content = fs::read_to_string(path) .map_err(|e| format!("Failed to read {}: {}", path.display(), e))?; - let filename = path.file_name() + let filename = path + .file_name() .map(|f| f.to_string_lossy().to_string()) .unwrap_or_default(); @@ -373,17 +395,32 @@ pub fn parse_md_file(path: &Path) -> Result { Ok(VaultEntry { path: path.to_string_lossy().to_string(), - filename, title, is_a, snippet, relationships, - aliases: frontmatter.aliases.map(|a| a.into_vec()).unwrap_or_default(), - belongs_to: frontmatter.belongs_to.map(|b| b.into_vec()).unwrap_or_default(), - related_to: frontmatter.related_to.map(|r| r.into_vec()).unwrap_or_default(), + filename, + title, + is_a, + snippet, + relationships, + aliases: frontmatter + .aliases + .map(|a| a.into_vec()) + .unwrap_or_default(), + belongs_to: frontmatter + .belongs_to + .map(|b| b.into_vec()) + .unwrap_or_default(), + related_to: frontmatter + .related_to + .map(|r| r.into_vec()) + .unwrap_or_default(), status: frontmatter.status, owner: frontmatter.owner, cadence: frontmatter.cadence, archived: frontmatter.archived.unwrap_or(false), trashed: frontmatter.trashed.unwrap_or(false), trashed_at: frontmatter.trashed_at.as_deref().and_then(parse_iso_date), - modified_at, created_at, file_size, + modified_at, + created_at, + file_size, icon: frontmatter.icon, color: frontmatter.color, order: frontmatter.order, @@ -435,10 +472,8 @@ fn pod_to_json(pod: gray_matter::Pod) -> serde_json::Value { serde_json::Value::Array(arr.into_iter().map(pod_to_json).collect()) } gray_matter::Pod::Hash(map) => { - let obj: serde_json::Map = map - .into_iter() - .map(|(k, v)| (k, pod_to_json(v))) - .collect(); + let obj: serde_json::Map = + map.into_iter().map(|(k, v)| (k, pod_to_json(v))).collect(); serde_json::Value::Object(obj) } gray_matter::Pod::Null => serde_json::Value::Null, @@ -469,12 +504,7 @@ pub fn purge_trash(vault_path: &str) -> Result, String> { .filter_map(|e| e.ok()) { let path = entry.path(); - if !path.is_file() - || path - .extension() - .map(|ext| ext != "md") - .unwrap_or(true) - { + if !path.is_file() || path.extension().map(|ext| ext != "md").unwrap_or(true) { continue; } @@ -528,8 +558,7 @@ pub fn get_note_content(path: &str) -> Result { if !file_path.is_file() { return Err(format!("Path is not a file: {}", path)); } - fs::read_to_string(file_path) - .map_err(|e| format!("Failed to read {}: {}", path, e)) + fs::read_to_string(file_path).map_err(|e| format!("Failed to read {}: {}", path, e)) } /// Scan a directory recursively for .md files and return VaultEntry for each. @@ -601,7 +630,9 @@ fn run_git(vault: &Path, args: &[&str]) -> Option { /// Parse a git status porcelain line into (status_code, file_path). fn parse_porcelain_line(line: &str) -> Option<(&str, String)> { - if line.len() < 3 { return None; } + if line.len() < 3 { + return None; + } Some((&line[..2], line[3..].trim().to_string())) } @@ -612,7 +643,8 @@ fn is_new_file_status(status: &str) -> bool { /// Extract .md file paths from git diff --name-only output. fn collect_md_paths_from_diff(stdout: &str) -> Vec { - stdout.lines() + stdout + .lines() .filter(|line| !line.is_empty() && line.ends_with(".md")) .map(|line| line.to_string()) .collect() @@ -620,7 +652,8 @@ fn collect_md_paths_from_diff(stdout: &str) -> Vec { /// Extract .md file paths from git status --porcelain output. fn collect_md_paths_from_porcelain(stdout: &str) -> Vec { - stdout.lines() + stdout + .lines() .filter_map(parse_porcelain_line) .filter(|(_, path)| path.ends_with(".md")) .map(|(_, path)| path) @@ -651,7 +684,8 @@ fn git_uncommitted_new_files(vault: &Path) -> Vec { Some(s) => s, None => return Vec::new(), }; - stdout.lines() + stdout + .lines() .filter_map(parse_porcelain_line) .filter(|(status, path)| path.ends_with(".md") && is_new_file_status(status)) .map(|(_, path)| path) @@ -673,7 +707,8 @@ fn write_cache(vault: &Path, cache: &VaultCache) { fn to_relative_path(abs_path: &str, vault: &Path) -> String { let vault_str = vault.to_string_lossy(); let with_slash = format!("{}/", vault_str); - abs_path.strip_prefix(&with_slash) + abs_path + .strip_prefix(&with_slash) .or_else(|| abs_path.strip_prefix(vault_str.as_ref())) .unwrap_or(abs_path) .to_string() @@ -681,10 +716,15 @@ fn to_relative_path(abs_path: &str, vault: &Path) -> String { /// Parse .md files from a list of relative paths, skipping any that don't exist. fn parse_files_at(vault: &Path, rel_paths: &[String]) -> Vec { - rel_paths.iter() + rel_paths + .iter() .filter_map(|rel| { let abs = vault.join(rel); - if abs.is_file() { parse_md_file(&abs).ok() } else { None } + if abs.is_file() { + parse_md_file(&abs).ok() + } else { + None + } }) .collect() } @@ -692,7 +732,13 @@ fn parse_files_at(vault: &Path, rel_paths: &[String]) -> Vec { /// Sort entries by modified_at descending and write the cache. fn finalize_and_cache(vault: &Path, mut entries: Vec, hash: String) -> Vec { entries.sort_by(|a, b| b.modified_at.cmp(&a.modified_at)); - write_cache(vault, &VaultCache { commit_hash: hash, entries: entries.clone() }); + write_cache( + vault, + &VaultCache { + commit_hash: hash, + entries: entries.clone(), + }, + ); entries } @@ -700,7 +746,8 @@ fn finalize_and_cache(vault: &Path, mut entries: Vec, hash: String) fn update_same_commit(vault: &Path, cache: VaultCache) -> Vec { let new_files = git_uncommitted_new_files(vault); let mut entries = cache.entries; - let existing: std::collections::HashSet = entries.iter() + let existing: std::collections::HashSet = entries + .iter() .map(|e| to_relative_path(&e.path, vault)) .collect(); @@ -716,11 +763,17 @@ fn update_same_commit(vault: &Path, cache: VaultCache) -> Vec { } /// Handle different-commit cache: incremental update via git diff. -fn update_different_commit(vault: &Path, cache: VaultCache, current_hash: String) -> Vec { +fn update_different_commit( + vault: &Path, + cache: VaultCache, + current_hash: String, +) -> Vec { let changed_files = git_changed_files(vault, &cache.commit_hash, ¤t_hash); let changed_set: std::collections::HashSet = changed_files.iter().cloned().collect(); - let mut entries: Vec = cache.entries.into_iter() + let mut entries: Vec = cache + .entries + .into_iter() .filter(|e| !changed_set.contains(&to_relative_path(&e.path, vault))) .collect(); entries.extend(parse_files_at(vault, &changed_files)); @@ -733,7 +786,10 @@ fn update_different_commit(vault: &Path, cache: VaultCache, current_hash: String pub fn scan_vault_cached(vault_path: &str) -> Result, String> { let vault = Path::new(vault_path); if !vault.exists() || !vault.is_dir() { - return Err(format!("Vault path does not exist or is not a directory: {}", vault_path)); + return Err(format!( + "Vault path does not exist or is not a directory: {}", + vault_path + )); } let current_hash = match git_head_hash(vault) { @@ -758,13 +814,21 @@ pub fn scan_vault_cached(vault_path: &str) -> Result, String> { pub use crate::frontmatter::FrontmatterValue; /// Update a single frontmatter property in a markdown file -pub fn update_frontmatter(path: &str, key: &str, value: FrontmatterValue) -> Result { - with_frontmatter(path, |content| update_frontmatter_content(content, key, Some(value.clone()))) +pub fn update_frontmatter( + path: &str, + key: &str, + value: FrontmatterValue, +) -> Result { + with_frontmatter(path, |content| { + update_frontmatter_content(content, key, Some(value.clone())) + }) } /// Delete a frontmatter property from a markdown file pub fn delete_frontmatter_property(path: &str, key: &str) -> Result { - with_frontmatter(path, |content| update_frontmatter_content(content, key, None)) + with_frontmatter(path, |content| { + update_frontmatter_content(content, key, None) + }) } /// Check if a character is safe for use in filenames (alphanumeric, dot, dash, underscore). @@ -802,8 +866,7 @@ pub fn save_image(vault_path: &str, filename: &str, data: &str) -> Result String { return content.to_string(); } - let result: Vec = content.lines().map(|l| { - if l.trim().starts_with("# ") { format!("# {}", new_title) } else { l.to_string() } - }).collect(); + let result: Vec = content + .lines() + .map(|l| { + if l.trim().starts_with("# ") { + format!("# {}", new_title) + } else { + l.to_string() + } + }) + .collect(); let joined = result.join("\n"); if content.ends_with('\n') && !joined.ends_with('\n') { @@ -861,9 +931,7 @@ fn build_wikilink_pattern(old_title: &str, old_path_stem: &str) -> Option /// Check if a path is a vault markdown file eligible for wikilink replacement. fn is_replaceable_md_file(path: &Path, exclude: &Path) -> bool { - path.is_file() - && path != exclude - && path.extension().is_some_and(|ext| ext == "md") + path.is_file() && path != exclude && path.extension().is_some_and(|ext| ext == "md") } /// Replace wikilink references in a single file's content. Returns updated content if changed. @@ -871,13 +939,15 @@ fn replace_wikilinks_in_content(content: &str, re: &Regex, new_title: &str) -> O if !re.is_match(content) { return None; } - let replaced = re.replace_all(content, |caps: ®ex::Captures| { - match caps.get(1) { - Some(pipe) => format!("[[{}{}]]", new_title, pipe.as_str()), - None => format!("[[{}]]", new_title), - } + let replaced = re.replace_all(content, |caps: ®ex::Captures| match caps.get(1) { + Some(pipe) => format!("[[{}{}]]", new_title, pipe.as_str()), + None => format!("[[{}]]", new_title), }); - if replaced != content { Some(replaced.into_owned()) } else { None } + if replaced != content { + Some(replaced.into_owned()) + } else { + None + } } /// Parameters for a vault-wide wikilink replacement. @@ -908,16 +978,19 @@ fn update_wikilinks_in_vault(params: &WikilinkReplacement) -> usize { }; let files = collect_md_files(params.vault_path, params.exclude_path); - files.iter().filter(|path| { - let content = match fs::read_to_string(path) { - Ok(c) => c, - Err(_) => return false, - }; - match replace_wikilinks_in_content(&content, &re, params.new_title) { - Some(new_content) => fs::write(path, &new_content).is_ok(), - None => false, - } - }).count() + files + .iter() + .filter(|path| { + let content = match fs::read_to_string(path) { + Ok(c) => c, + Err(_) => return false, + }; + match replace_wikilinks_in_content(&content, &re, params.new_title) { + Some(new_content) => fs::write(path, &new_content).is_ok(), + None => false, + } + }) + .count() } /// Check if frontmatter contains a `title:` key. @@ -925,11 +998,15 @@ fn frontmatter_has_title_key(content: &str) -> bool { if !content.starts_with("---\n") { return false; } - content[4..].split("\n---").next() - .map(|fm| fm.lines().any(|l| { - let t = l.trim_start(); - t.starts_with("title:") || t.starts_with("\"title\":") - })) + content[4..] + .split("\n---") + .next() + .map(|fm| { + fm.lines().any(|l| { + let t = l.trim_start(); + t.starts_with("title:") || t.starts_with("\"title\":") + }) + }) .unwrap_or(false) } @@ -955,7 +1032,11 @@ fn to_path_stem<'a>(abs_path: &'a str, vault_prefix: &str) -> &'a str { } /// Rename a note: update its title, rename the file, and update wiki links across the vault. -pub fn rename_note(vault_path: &str, old_path: &str, new_title: &str) -> Result { +pub fn rename_note( + vault_path: &str, + old_path: &str, + new_title: &str, +) -> Result { let vault = Path::new(vault_path); let old_file = Path::new(old_path); @@ -967,21 +1048,28 @@ pub fn rename_note(vault_path: &str, old_path: &str, new_title: &str) -> Result< return Err("New title cannot be empty".to_string()); } - let content = fs::read_to_string(old_file) - .map_err(|e| format!("Failed to read {}: {}", old_path, e))?; - let old_filename = old_file.file_name() - .map(|f| f.to_string_lossy().to_string()).unwrap_or_default(); + let content = + fs::read_to_string(old_file).map_err(|e| format!("Failed to read {}: {}", old_path, e))?; + let old_filename = old_file + .file_name() + .map(|f| f.to_string_lossy().to_string()) + .unwrap_or_default(); let old_title = extract_title(&content, &old_filename); if old_title == new_title { - return Ok(RenameResult { new_path: old_path.to_string(), updated_files: 0 }); + return Ok(RenameResult { + new_path: old_path.to_string(), + updated_files: 0, + }); } // Update content (H1 + frontmatter title) let updated_content = update_note_title_in_content(&content, new_title); // Compute new path and write file - let parent_dir = old_file.parent().ok_or("Cannot determine parent directory")?; + let parent_dir = old_file + .parent() + .ok_or("Cannot determine parent directory")?; let new_file = parent_dir.join(format!("{}.md", title_to_slug(new_title))); let new_path_str = new_file.to_string_lossy().to_string(); @@ -996,10 +1084,17 @@ pub fn rename_note(vault_path: &str, old_path: &str, new_title: &str) -> Result< let vault_prefix = format!("{}/", vault.to_string_lossy()); let old_path_stem = to_path_stem(old_path, &vault_prefix); let updated_files = update_wikilinks_in_vault(&WikilinkReplacement { - vault_path: vault, old_title: &old_title, new_title, old_path_stem, exclude_path: &new_file, + vault_path: vault, + old_title: &old_title, + new_title, + old_path_stem, + exclude_path: &new_file, }); - Ok(RenameResult { new_path: new_path_str, updated_files }) + Ok(RenameResult { + new_path: new_path_str, + updated_files, + }) } #[cfg(test)] @@ -1027,7 +1122,10 @@ mod tests { #[test] fn test_extract_title_fallback_to_filename() { let content = "Just some content without a heading."; - assert_eq!(extract_title(content, "fallback-title.md"), "fallback-title"); + assert_eq!( + extract_title(content, "fallback-title.md"), + "fallback-title" + ); } #[test] @@ -1073,7 +1171,11 @@ mod tests { #[test] fn test_parse_empty_frontmatter() { let dir = TempDir::new().unwrap(); - let entry = parse_test_entry(&dir, "empty-fm.md", "---\n---\n# Just a Title\n\nNo frontmatter fields."); + let entry = parse_test_entry( + &dir, + "empty-fm.md", + "---\n---\n# Just a Title\n\nNo frontmatter fields.", + ); assert_eq!(entry.title, "Just a Title"); assert!(entry.aliases.is_empty()); @@ -1106,7 +1208,11 @@ mod tests { fn test_scan_vault_recursive() { let dir = TempDir::new().unwrap(); create_test_file(dir.path(), "root.md", "# Root Note\n"); - create_test_file(dir.path(), "sub/nested.md", "---\nIs A: Task\n---\n# Nested\n"); + create_test_file( + dir.path(), + "sub/nested.md", + "---\nIs A: Task\n---\n# Nested\n", + ); create_test_file(dir.path(), "not-markdown.txt", "This should be ignored"); let entries = scan_vault(dir.path().to_str().unwrap()).unwrap(); @@ -1219,13 +1325,33 @@ mod tests { let vault = dir.path(); // Init git repo - std::process::Command::new("git").args(["init"]).current_dir(vault).output().unwrap(); - std::process::Command::new("git").args(["config", "user.email", "test@test.com"]).current_dir(vault).output().unwrap(); - std::process::Command::new("git").args(["config", "user.name", "Test"]).current_dir(vault).output().unwrap(); + std::process::Command::new("git") + .args(["init"]) + .current_dir(vault) + .output() + .unwrap(); + std::process::Command::new("git") + .args(["config", "user.email", "test@test.com"]) + .current_dir(vault) + .output() + .unwrap(); + std::process::Command::new("git") + .args(["config", "user.name", "Test"]) + .current_dir(vault) + .output() + .unwrap(); create_test_file(vault, "note.md", "# Note\n\nFirst version."); - std::process::Command::new("git").args(["add", "."]).current_dir(vault).output().unwrap(); - std::process::Command::new("git").args(["commit", "-m", "init"]).current_dir(vault).output().unwrap(); + std::process::Command::new("git") + .args(["add", "."]) + .current_dir(vault) + .output() + .unwrap(); + std::process::Command::new("git") + .args(["commit", "-m", "init"]) + .current_dir(vault) + .output() + .unwrap(); // First call: full scan, writes cache let entries = scan_vault_cached(vault.to_str().unwrap()).unwrap(); @@ -1259,7 +1385,10 @@ Status: Active assert_eq!(entry.relationships.len(), 3); // Has, Topics, Type assert_eq!( entry.relationships.get("Has").unwrap(), - &vec!["[[essay/foo|Foo Essay]]".to_string(), "[[essay/bar|Bar Essay]]".to_string()] + &vec![ + "[[essay/foo|Foo Essay]]".to_string(), + "[[essay/bar|Bar Essay]]".to_string() + ] ); assert_eq!( entry.relationships.get("Topics").unwrap(), @@ -1343,7 +1472,10 @@ Custom Field: just a plain string fn test_parse_relationships_owner_and_notes() { let rels = parse_big_project_rels(); assert_eq!(rels.get("Notes").unwrap().len(), 3); - assert_eq!(rels.get("Owner").unwrap(), &vec!["[[person/alice]]".to_string()]); + assert_eq!( + rels.get("Owner").unwrap(), + &vec!["[[person/alice]]".to_string()] + ); } #[test] @@ -1385,7 +1517,10 @@ Context: "[[area/research]]" // Array → Vec with multiple elements assert_eq!( entry.relationships.get("Reviewers").unwrap(), - &vec!["[[person/carol]]".to_string(), "[[person/dave]]".to_string()] + &vec![ + "[[person/carol]]".to_string(), + "[[person/dave]]".to_string() + ] ); // Another single string assert_eq!( @@ -1422,7 +1557,10 @@ Context: "[[area/research]]" #[test] fn test_skip_keys_real_relation_included() { let (rels, len) = parse_skip_keys_rels(); - assert_eq!(rels.get("Real Relation").unwrap(), &vec!["[[note/important]]".to_string()]); + assert_eq!( + rels.get("Real Relation").unwrap(), + &vec!["[[note/important]]".to_string()] + ); // "Real Relation" + auto-generated "Type" (from is_a: "[[type/project]]") assert_eq!(len, 2); assert_eq!( @@ -1451,7 +1589,10 @@ References: // Only the wikilink entries should be captured assert_eq!( entry.relationships.get("References").unwrap(), - &vec!["[[source/paper-a]]".to_string(), "[[source/paper-b]]".to_string()] + &vec![ + "[[source/paper-a]]".to_string(), + "[[source/paper-b]]".to_string() + ] ); } @@ -1508,7 +1649,10 @@ References: #[test] fn test_strip_markdown_chars_emphasis() { - assert_eq!(strip_markdown_chars("**bold** and *italic*"), "bold and italic"); + assert_eq!( + strip_markdown_chars("**bold** and *italic*"), + "bold and italic" + ); } #[test] @@ -1523,7 +1667,10 @@ References: #[test] fn test_strip_markdown_chars_link_with_url() { - assert_eq!(strip_markdown_chars("[click here](https://example.com)"), "click here"); + assert_eq!( + strip_markdown_chars("[click here](https://example.com)"), + "click here" + ); } #[test] @@ -1591,7 +1738,13 @@ References: for (folder, expected_type) in known_folders { create_test_file(dir.path(), &format!("{}/test.md", folder), "# Test\n"); let entry = parse_md_file(&dir.path().join(folder).join("test.md")).unwrap(); - assert_eq!(entry.is_a, Some(expected_type.to_string()), "folder '{}' should infer type '{}'", folder, expected_type); + assert_eq!( + entry.is_a, + Some(expected_type.to_string()), + "folder '{}' should infer type '{}'", + folder, + expected_type + ); } } @@ -1606,7 +1759,11 @@ References: #[test] fn test_infer_type_frontmatter_overrides_folder() { let dir = TempDir::new().unwrap(); - create_test_file(dir.path(), "person/test.md", "---\nIs A: Custom\n---\n# Test\n"); + create_test_file( + dir.path(), + "person/test.md", + "---\nIs A: Custom\n---\n# Test\n", + ); let entry = parse_md_file(&dir.path().join("person/test.md")).unwrap(); assert_eq!(entry.is_a, Some("Custom".to_string())); } @@ -1715,13 +1872,33 @@ References: let vault = dir.path(); // Init git repo - std::process::Command::new("git").args(["init"]).current_dir(vault).output().unwrap(); - std::process::Command::new("git").args(["config", "user.email", "test@test.com"]).current_dir(vault).output().unwrap(); - std::process::Command::new("git").args(["config", "user.name", "Test"]).current_dir(vault).output().unwrap(); + std::process::Command::new("git") + .args(["init"]) + .current_dir(vault) + .output() + .unwrap(); + std::process::Command::new("git") + .args(["config", "user.email", "test@test.com"]) + .current_dir(vault) + .output() + .unwrap(); + std::process::Command::new("git") + .args(["config", "user.name", "Test"]) + .current_dir(vault) + .output() + .unwrap(); create_test_file(vault, "first.md", "# First\n\nFirst note."); - std::process::Command::new("git").args(["add", "."]).current_dir(vault).output().unwrap(); - std::process::Command::new("git").args(["commit", "-m", "first"]).current_dir(vault).output().unwrap(); + std::process::Command::new("git") + .args(["add", "."]) + .current_dir(vault) + .output() + .unwrap(); + std::process::Command::new("git") + .args(["commit", "-m", "first"]) + .current_dir(vault) + .output() + .unwrap(); // Build cache let entries = scan_vault_cached(vault.to_str().unwrap()).unwrap(); @@ -1729,8 +1906,16 @@ References: // Add a second file and commit create_test_file(vault, "second.md", "# Second\n\nSecond note."); - std::process::Command::new("git").args(["add", "."]).current_dir(vault).output().unwrap(); - std::process::Command::new("git").args(["commit", "-m", "second"]).current_dir(vault).output().unwrap(); + std::process::Command::new("git") + .args(["add", "."]) + .current_dir(vault) + .output() + .unwrap(); + std::process::Command::new("git") + .args(["commit", "-m", "second"]) + .current_dir(vault) + .output() + .unwrap(); // Incremental update: cache has old commit, new commit adds second.md let entries2 = scan_vault_cached(vault.to_str().unwrap()).unwrap(); @@ -1886,7 +2071,10 @@ References: "---\nTrashed at: \"2025-01-01\"\n---\n# Old Trash\n", ); // File trashed recently — should be kept - let recent = chrono::Utc::now().date_naive().format("%Y-%m-%d").to_string(); + let recent = chrono::Utc::now() + .date_naive() + .format("%Y-%m-%d") + .to_string(); create_test_file( dir.path(), "recent-trash.md", @@ -1939,14 +2127,16 @@ References: #[test] fn test_purge_trash_exactly_30_days_not_deleted() { let dir = TempDir::new().unwrap(); - let thirty_days_ago = (chrono::Utc::now().date_naive() - - chrono::Duration::days(30)) + let thirty_days_ago = (chrono::Utc::now().date_naive() - chrono::Duration::days(30)) .format("%Y-%m-%d") .to_string(); create_test_file( dir.path(), "borderline.md", - &format!("---\nTrashed at: \"{}\"\n---\n# Borderline\n", thirty_days_ago), + &format!( + "---\nTrashed at: \"{}\"\n---\n# Borderline\n", + thirty_days_ago + ), ); let deleted = purge_trash(dir.path().to_str().unwrap()).unwrap(); @@ -1957,14 +2147,16 @@ References: #[test] fn test_purge_trash_31_days_deleted() { let dir = TempDir::new().unwrap(); - let thirty_one_days_ago = (chrono::Utc::now().date_naive() - - chrono::Duration::days(31)) + let thirty_one_days_ago = (chrono::Utc::now().date_naive() - chrono::Duration::days(31)) .format("%Y-%m-%d") .to_string(); create_test_file( dir.path(), "expired.md", - &format!("---\nTrashed at: \"{}\"\n---\n# Expired\n", thirty_one_days_ago), + &format!( + "---\nTrashed at: \"{}\"\n---\n# Expired\n", + thirty_one_days_ago + ), ); let deleted = purge_trash(dir.path().to_str().unwrap()).unwrap(); @@ -2008,14 +2200,19 @@ References: fn test_rename_note_basic() { let dir = TempDir::new().unwrap(); let vault = dir.path(); - create_test_file(vault, "note/weekly-review.md", "---\nIs A: Note\n---\n# Weekly Review\n\nContent here.\n"); + create_test_file( + vault, + "note/weekly-review.md", + "---\nIs A: Note\n---\n# Weekly Review\n\nContent here.\n", + ); let old_path = vault.join("note/weekly-review.md"); let result = rename_note( vault.to_str().unwrap(), old_path.to_str().unwrap(), "Sprint Retrospective", - ).unwrap(); + ) + .unwrap(); assert!(result.new_path.ends_with("sprint-retrospective.md")); assert!(!old_path.exists()); @@ -2029,16 +2226,29 @@ References: fn test_rename_note_updates_wikilinks() { let dir = TempDir::new().unwrap(); let vault = dir.path(); - create_test_file(vault, "note/weekly-review.md", "---\nIs A: Note\n---\n# Weekly Review\n\nContent.\n"); - create_test_file(vault, "note/other.md", "---\nIs A: Note\n---\n# Other\n\nSee [[Weekly Review]] for details.\n"); - create_test_file(vault, "project/my-project.md", "---\nIs A: Project\nRelated to:\n - \"[[Weekly Review]]\"\n---\n# My Project\n"); + create_test_file( + vault, + "note/weekly-review.md", + "---\nIs A: Note\n---\n# Weekly Review\n\nContent.\n", + ); + create_test_file( + vault, + "note/other.md", + "---\nIs A: Note\n---\n# Other\n\nSee [[Weekly Review]] for details.\n", + ); + create_test_file( + vault, + "project/my-project.md", + "---\nIs A: Project\nRelated to:\n - \"[[Weekly Review]]\"\n---\n# My Project\n", + ); let old_path = vault.join("note/weekly-review.md"); let result = rename_note( vault.to_str().unwrap(), old_path.to_str().unwrap(), "Sprint Retrospective", - ).unwrap(); + ) + .unwrap(); assert_eq!(result.updated_files, 2); @@ -2061,7 +2271,8 @@ References: vault.to_str().unwrap(), old_path.to_str().unwrap(), "My Note", - ).unwrap(); + ) + .unwrap(); assert_eq!(result.new_path, old_path.to_str().unwrap()); assert_eq!(result.updated_files, 0); @@ -2074,11 +2285,7 @@ References: create_test_file(vault, "note/test.md", "# Test\n"); let old_path = vault.join("note/test.md"); - let result = rename_note( - vault.to_str().unwrap(), - old_path.to_str().unwrap(), - " ", - ); + let result = rename_note(vault.to_str().unwrap(), old_path.to_str().unwrap(), " "); assert!(result.is_err()); } @@ -2087,14 +2294,19 @@ References: let dir = TempDir::new().unwrap(); let vault = dir.path(); create_test_file(vault, "note/weekly-review.md", "# Weekly Review\n"); - create_test_file(vault, "note/ref.md", "# Ref\n\nSee [[Weekly Review|my review]] for info.\n"); + create_test_file( + vault, + "note/ref.md", + "# Ref\n\nSee [[Weekly Review|my review]] for info.\n", + ); let old_path = vault.join("note/weekly-review.md"); let result = rename_note( vault.to_str().unwrap(), old_path.to_str().unwrap(), "Sprint Retro", - ).unwrap(); + ) + .unwrap(); assert_eq!(result.updated_files, 1); let ref_content = fs::read_to_string(vault.join("note/ref.md")).unwrap(); @@ -2105,14 +2317,19 @@ References: fn test_rename_note_updates_title_frontmatter() { let dir = TempDir::new().unwrap(); let vault = dir.path(); - create_test_file(vault, "note/old.md", "---\ntitle: Old Name\nIs A: Note\n---\n# Old Name\n"); + create_test_file( + vault, + "note/old.md", + "---\ntitle: Old Name\nIs A: Note\n---\n# Old Name\n", + ); let old_path = vault.join("note/old.md"); let result = rename_note( vault.to_str().unwrap(), old_path.to_str().unwrap(), "New Name", - ).unwrap(); + ) + .unwrap(); let content = fs::read_to_string(&result.new_path).unwrap(); assert!(content.contains("title: New Name"));