refactor: remove dead code from flat vault migration

- Remove move_note_to_type_folder function and all its tests
- Remove MoveResult, type_to_folder_slug from rename.rs
- Remove infer_type_from_folder, capitalize_first, title_case_folder
- These were all made dead by the flat vault migration

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
lucaronin
2026-03-15 19:55:23 +01:00
parent 094e806b13
commit 0d23d9f001
3 changed files with 1 additions and 490 deletions

View File

@@ -17,7 +17,7 @@ pub use trash::{batch_delete_notes, delete_note, empty_trash, is_file_trashed, p
use parsing::{
contains_wikilink, count_body_words, extract_outgoing_links, extract_snippet, extract_title,
parse_iso_date, title_case_folder,
parse_iso_date,
};
use gray_matter::engine::YAML;
@@ -322,31 +322,6 @@ fn extract_properties(
properties
}
/// Infer entity type from a parent folder name.
fn infer_type_from_folder(folder: &str) -> String {
match folder {
"person" => "Person",
"project" => "Project",
"procedure" => "Procedure",
"responsibility" => "Responsibility",
"event" => "Event",
"topic" => "Topic",
"experiment" => "Experiment",
"type" => "Type",
"note" => "Note",
"quarter" => "Quarter",
"measure" => "Measure",
"target" => "Target",
"journal" => "Journal",
"month" => "Month",
"config" => "Config",
"essay" => "Essay",
"evergreen" => "Evergreen",
_ => return title_case_folder(folder),
}
.to_string()
}
/// Resolve `is_a` from frontmatter only. Folder-based inference is no longer used
/// (flat vault: all notes live at vault root, type is purely frontmatter).
fn resolve_is_a(fm_is_a: Option<StringOrList>, _path: &Path) -> Option<String> {

View File

@@ -193,24 +193,6 @@ pub(super) fn extract_outgoing_links(content: &str) -> Vec<String> {
links
}
pub(super) fn capitalize_first(s: &str) -> String {
let mut c = s.chars();
match c.next() {
None => String::new(),
Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
}
}
/// Title-case a folder name: split on hyphens and underscores, capitalize each word, join with spaces.
/// Example: "monday-ideas" → "Monday Ideas", "key_result" → "Key Result"
pub(super) fn title_case_folder(s: &str) -> String {
s.split(['-', '_'])
.filter(|w| !w.is_empty())
.map(capitalize_first)
.collect::<Vec<_>>()
.join(" ")
}
/// Parse an ISO 8601 date string to Unix timestamp (seconds since epoch).
/// Handles "2025-05-23T14:35:00.000Z" and "2025-05-23" formats.
pub(super) fn parse_iso_date(date_str: &str) -> Option<u64> {
@@ -491,51 +473,6 @@ mod tests {
assert_eq!(strip_markdown_chars(""), "");
}
// --- capitalize_first tests ---
#[test]
fn test_capitalize_first_normal() {
assert_eq!(capitalize_first("person"), "Person");
}
#[test]
fn test_capitalize_first_already_capitalized() {
assert_eq!(capitalize_first("Project"), "Project");
}
#[test]
fn test_capitalize_first_empty() {
assert_eq!(capitalize_first(""), "");
}
#[test]
fn test_capitalize_first_single_char() {
assert_eq!(capitalize_first("a"), "A");
}
// --- title_case_folder tests ---
#[test]
fn test_title_case_folder_hyphenated() {
assert_eq!(title_case_folder("monday-ideas"), "Monday Ideas");
assert_eq!(title_case_folder("key-result"), "Key Result");
}
#[test]
fn test_title_case_folder_underscored() {
assert_eq!(title_case_folder("my_custom_type"), "My Custom Type");
}
#[test]
fn test_title_case_folder_single_word() {
assert_eq!(title_case_folder("recipe"), "Recipe");
}
#[test]
fn test_title_case_folder_empty() {
assert_eq!(title_case_folder(""), "");
}
// --- without_h1_line tests ---
#[test]

View File

@@ -166,31 +166,6 @@ fn to_path_stem<'a>(abs_path: &'a str, vault_prefix: &str) -> &'a str {
.unwrap_or(abs_path)
}
/// Result of a move-to-type-folder operation.
#[derive(Debug, Serialize, Deserialize)]
pub struct MoveResult {
/// New absolute file path after move (same as old if no move happened).
pub new_path: String,
/// Number of other files updated (wikilink replacements).
pub updated_links: usize,
/// Whether the file was actually moved (false if already in the right folder).
pub moved: bool,
}
/// Convert a type name to a folder slug. All known types are single lowercase words;
/// unknown types are slugified (lowercase, non-alphanumeric → hyphen).
fn type_to_folder_slug(type_name: &str) -> String {
type_name
.to_lowercase()
.chars()
.map(|c| if c.is_ascii_alphanumeric() { c } else { '-' })
.collect::<String>()
.split('-')
.filter(|s| !s.is_empty())
.collect::<Vec<&str>>()
.join("-")
}
/// Determine a unique destination path, appending -2, -3, etc. if a file already exists.
fn unique_dest_path(dest_dir: &Path, filename: &str) -> std::path::PathBuf {
let dest = dest_dir.join(filename);
@@ -215,134 +190,6 @@ fn unique_dest_path(dest_dir: &Path, filename: &str) -> std::path::PathBuf {
}
}
/// Move a note to the folder corresponding to its new type, and update wikilinks across the vault.
///
/// Returns `MoveResult` with `moved: false` if the note is already in the correct folder.
/// Creates the target folder if it does not exist.
pub fn move_note_to_type_folder(
vault_path: &str,
note_path: &str,
new_type: &str,
) -> Result<MoveResult, String> {
let vault = Path::new(vault_path);
let old_file = Path::new(note_path);
if !old_file.exists() {
return Err(format!("File does not exist: {}", note_path));
}
let new_type = new_type.trim();
if new_type.is_empty() {
return Err("Type cannot be empty".to_string());
}
let folder_slug = type_to_folder_slug(new_type);
// Check if already in the correct folder
let current_folder = old_file
.parent()
.and_then(|p| p.file_name())
.map(|f| f.to_string_lossy().to_string())
.unwrap_or_default();
if current_folder == folder_slug {
return Ok(MoveResult {
new_path: note_path.to_string(),
updated_links: 0,
moved: false,
});
}
let filename = old_file
.file_name()
.map(|f| f.to_string_lossy().to_string())
.unwrap_or_default();
// Create target directory if needed
let dest_dir = vault.join(&folder_slug);
if !dest_dir.exists() {
fs::create_dir_all(&dest_dir)
.map_err(|e| format!("Failed to create directory {}: {}", dest_dir.display(), e))?;
}
// Determine destination path (handle collisions)
let new_file = unique_dest_path(&dest_dir, &filename);
let new_path_str = new_file.to_string_lossy().to_string();
// Read content and move
let content =
fs::read_to_string(old_file).map_err(|e| format!("Failed to read {}: {}", note_path, e))?;
fs::write(&new_file, &content)
.map_err(|e| format!("Failed to write {}: {}", new_path_str, e))?;
fs::remove_file(old_file)
.map_err(|e| format!("Failed to remove old file {}: {}", note_path, e))?;
// Extract title for wikilink matching
let old_filename = old_file
.file_name()
.map(|f| f.to_string_lossy().to_string())
.unwrap_or_default();
let old_title = super::extract_title(&content, &old_filename);
// Update wikilinks across the vault (title stays the same, path changes)
let vault_prefix = format!("{}/", vault.to_string_lossy());
let old_path_stem = to_path_stem(note_path, &vault_prefix);
let new_path_stem = to_path_stem(&new_path_str, &vault_prefix);
// Build pattern matching old path stem (e.g. "note/weekly-review")
let re = match build_wikilink_pattern(&old_title, old_path_stem) {
Some(r) => r,
None => {
return Ok(MoveResult {
new_path: new_path_str,
updated_links: 0,
moved: true,
})
}
};
// Determine the replacement: if path-style wikilinks were used, update to new path.
// Title-style wikilinks [[My Note]] stay the same (title hasn't changed).
let files = collect_md_files(vault, &new_file);
let updated_links = files
.iter()
.filter(|path| {
let file_content = match fs::read_to_string(path) {
Ok(c) => c,
Err(_) => return false,
};
if !re.is_match(&file_content) {
return false;
}
// Replace path-based wikilinks (old_path_stem → new_path_stem)
// and keep title-based wikilinks as-is.
let replaced = re.replace_all(&file_content, |caps: &regex::Captures| {
let full_match = caps.get(0).map(|m| m.as_str()).unwrap_or("");
let pipe = caps.get(1);
// If the match used the path stem, replace with new path stem
if full_match.contains(old_path_stem) {
match pipe {
Some(p) => format!("[[{}{}]]", new_path_stem, p.as_str()),
None => format!("[[{}]]", new_path_stem),
}
} else {
// Title-based link — keep as-is (title hasn't changed)
full_match.to_string()
}
});
if replaced != file_content {
fs::write(path, replaced.as_ref()).is_ok()
} else {
false
}
})
.count();
Ok(MoveResult {
new_path: new_path_str,
updated_links,
moved: true,
})
}
/// Rename a note: update its title, rename the file, and update wiki links across the vault.
///
/// When `old_title_hint` is provided it is used instead of extracting the title from
@@ -753,213 +600,6 @@ mod tests {
assert!(vault.join("note/my-note.md").exists());
}
// --- move_note_to_type_folder tests ---
#[test]
fn test_type_to_folder_slug_known_types() {
assert_eq!(type_to_folder_slug("Person"), "person");
assert_eq!(type_to_folder_slug("Project"), "project");
assert_eq!(type_to_folder_slug("Quarter"), "quarter");
assert_eq!(type_to_folder_slug("Note"), "note");
}
#[test]
fn test_type_to_folder_slug_unknown_types() {
assert_eq!(type_to_folder_slug("Key Result"), "key-result");
assert_eq!(type_to_folder_slug("My Custom Type"), "my-custom-type");
}
#[test]
fn test_move_note_basic() {
let dir = TempDir::new().unwrap();
let vault = dir.path();
create_test_file(
vault,
"note/weekly-review.md",
"---\ntype: Quarter\n---\n# Weekly Review\n\nContent here.\n",
);
let old_path = vault.join("note/weekly-review.md");
let result = move_note_to_type_folder(
vault.to_str().unwrap(),
old_path.to_str().unwrap(),
"Quarter",
)
.unwrap();
assert!(result.moved);
assert!(result.new_path.contains("/quarter/weekly-review.md"));
assert!(!old_path.exists());
assert!(Path::new(&result.new_path).exists());
}
#[test]
fn test_move_note_already_in_correct_folder() {
let dir = TempDir::new().unwrap();
let vault = dir.path();
create_test_file(
vault,
"quarter/weekly-review.md",
"---\ntype: Quarter\n---\n# Weekly Review\n",
);
let path = vault.join("quarter/weekly-review.md");
let result =
move_note_to_type_folder(vault.to_str().unwrap(), path.to_str().unwrap(), "Quarter")
.unwrap();
assert!(!result.moved);
assert_eq!(result.new_path, path.to_str().unwrap());
assert_eq!(result.updated_links, 0);
}
#[test]
fn test_move_note_creates_target_folder() {
let dir = TempDir::new().unwrap();
let vault = dir.path();
create_test_file(
vault,
"note/my-note.md",
"---\ntype: Quarter\n---\n# My Note\n",
);
let dest_dir = vault.join("quarter");
assert!(!dest_dir.exists());
let old_path = vault.join("note/my-note.md");
let result = move_note_to_type_folder(
vault.to_str().unwrap(),
old_path.to_str().unwrap(),
"Quarter",
)
.unwrap();
assert!(result.moved);
assert!(dest_dir.exists());
assert!(Path::new(&result.new_path).exists());
}
#[test]
fn test_move_note_filename_collision() {
let dir = TempDir::new().unwrap();
let vault = dir.path();
create_test_file(
vault,
"note/my-note.md",
"---\ntype: Quarter\n---\n# My Note\n",
);
create_test_file(
vault,
"quarter/my-note.md",
"---\ntype: Quarter\n---\n# Existing Note\n",
);
let old_path = vault.join("note/my-note.md");
let result = move_note_to_type_folder(
vault.to_str().unwrap(),
old_path.to_str().unwrap(),
"Quarter",
)
.unwrap();
assert!(result.moved);
assert!(result.new_path.contains("/quarter/my-note-2.md"));
assert!(!old_path.exists());
assert!(Path::new(&result.new_path).exists());
// Original file should still exist
assert!(vault.join("quarter/my-note.md").exists());
}
#[test]
fn test_move_note_updates_path_wikilinks() {
let dir = TempDir::new().unwrap();
let vault = dir.path();
create_test_file(
vault,
"note/weekly-review.md",
"---\ntype: Quarter\n---\n# Weekly Review\n\nContent.\n",
);
create_test_file(
vault,
"project/my-project.md",
"---\ntype: Project\n---\n# My Project\n\nSee [[note/weekly-review]] for details.\n",
);
let old_path = vault.join("note/weekly-review.md");
let result = move_note_to_type_folder(
vault.to_str().unwrap(),
old_path.to_str().unwrap(),
"Quarter",
)
.unwrap();
assert!(result.moved);
assert_eq!(result.updated_links, 1);
let project_content = fs::read_to_string(vault.join("project/my-project.md")).unwrap();
assert!(project_content.contains("[[quarter/weekly-review]]"));
assert!(!project_content.contains("[[note/weekly-review]]"));
}
#[test]
fn test_move_note_preserves_title_wikilinks() {
let dir = TempDir::new().unwrap();
let vault = dir.path();
create_test_file(
vault,
"note/weekly-review.md",
"---\ntype: Quarter\n---\n# Weekly Review\n",
);
create_test_file(
vault,
"note/other.md",
"---\ntype: Note\n---\n# Other\n\nSee [[Weekly Review]] for details.\n",
);
let old_path = vault.join("note/weekly-review.md");
let result = move_note_to_type_folder(
vault.to_str().unwrap(),
old_path.to_str().unwrap(),
"Quarter",
)
.unwrap();
assert!(result.moved);
// Title-based wikilinks should be unchanged
let other_content = fs::read_to_string(vault.join("note/other.md")).unwrap();
assert!(other_content.contains("[[Weekly Review]]"));
}
#[test]
fn test_move_note_collision_preserves_both_contents() {
let dir = TempDir::new().unwrap();
let vault = dir.path();
let moving_content =
"---\ntype: Quarter\n---\n# Migrate newsletter to Beehiiv\n\nImportant content.\n";
let existing_content =
"---\ntype: Quarter\n---\n# Feedback for Laputa\n\nCompletely different note.\n";
create_test_file(vault, "note/my-note.md", moving_content);
create_test_file(vault, "quarter/my-note.md", existing_content);
let old_path = vault.join("note/my-note.md");
let result = move_note_to_type_folder(
vault.to_str().unwrap(),
old_path.to_str().unwrap(),
"Quarter",
)
.unwrap();
assert!(result.moved);
// Must get a unique path, not the existing file's path
assert!(result.new_path.contains("/quarter/my-note-2.md"));
// Moved note must retain its own content
let moved_content = fs::read_to_string(&result.new_path).unwrap();
assert_eq!(moved_content, moving_content);
// Existing note must be untouched
let untouched = fs::read_to_string(vault.join("quarter/my-note.md")).unwrap();
assert_eq!(untouched, existing_content);
}
#[test]
fn test_rename_note_with_old_title_hint_updates_wikilinks() {
// Simulates H1 sync: content already saved with new H1, but wikilinks still use old title.
@@ -1055,45 +695,4 @@ mod tests {
assert_eq!(result.updated_files, 0);
}
#[test]
fn test_move_note_empty_type_error() {
let dir = TempDir::new().unwrap();
let vault = dir.path();
create_test_file(vault, "note/test.md", "# Test\n");
let path = vault.join("note/test.md");
let result = move_note_to_type_folder(vault.to_str().unwrap(), path.to_str().unwrap(), "");
assert!(result.is_err());
}
#[test]
fn test_move_note_nonexistent_file_error() {
let dir = TempDir::new().unwrap();
let vault = dir.path();
let result = move_note_to_type_folder(
vault.to_str().unwrap(),
vault.join("note/nope.md").to_str().unwrap(),
"Quarter",
);
assert!(result.is_err());
}
#[test]
fn test_move_note_preserves_content() {
let dir = TempDir::new().unwrap();
let vault = dir.path();
let original = "---\ntype: Quarter\ntitle: My Note\n---\n# My Note\n\nImportant content.\n";
create_test_file(vault, "note/my-note.md", original);
let old_path = vault.join("note/my-note.md");
let result = move_note_to_type_folder(
vault.to_str().unwrap(),
old_path.to_str().unwrap(),
"Quarter",
)
.unwrap();
let content = fs::read_to_string(&result.new_path).unwrap();
assert_eq!(content, original);
}
}