feat: move inline metadata to specific location in note (#27)

This commit is contained in:
Nathaniel Landau
2023-03-12 13:58:55 -04:00
committed by GitHub
parent 82e1cba34a
commit 8cefca2639
10 changed files with 562 additions and 251 deletions

View File

@@ -12,7 +12,7 @@ from rich.table import Table
from obsidian_metadata._config import VaultConfig
from obsidian_metadata._utils import alerts
from obsidian_metadata._utils.console import console
from obsidian_metadata.models import Vault, VaultFilter
from obsidian_metadata.models import InsertLocation, Vault, VaultFilter
from obsidian_metadata.models.enums import MetadataType
from obsidian_metadata.models.questions import Questions
@@ -63,8 +63,8 @@ class Application:
self.application_rename_metadata()
case "delete_metadata":
self.application_delete_metadata()
case "transpose_metadata":
self.application_transpose_metadata()
case "reorganize_metadata":
self.application_reorganize_metadata()
case "review_changes":
self.review_changes()
case "commit_changes":
@@ -332,11 +332,23 @@ class Application:
case _:
return
def application_transpose_metadata(self) -> None:
"""Transpose metadata."""
alerts.usage("Move metadata between types. i.e. from frontmatter to inline or vice versa.")
def application_reorganize_metadata(self) -> None:
"""Reorganize metadata.
This portion of the application deals with moving metadata between types (inline to frontmatter, etc.) and moving the location of inline metadata within a note.
"""
alerts.usage("Move metadata within notes.")
alerts.usage(" 1. Transpose frontmatter to inline or vice versa.")
alerts.usage(" 2. Move the location of inline metadata within a note.")
choices = [
{"name": "Move inline metadata to top of note", "value": "move_to_top"},
{
"name": "Move inline metadata beneath the first header",
"value": "move_to_after_header",
},
{"name": "Move inline metadata to bottom of the note", "value": "move_to_bottom"},
{"name": "Transpose frontmatter to inline", "value": "frontmatter_to_inline"},
{"name": "Transpose inline to frontmatter", "value": "inline_to_frontmatter"},
questionary.Separator(),
@@ -349,6 +361,12 @@ class Application:
self.transpose_metadata(begin=MetadataType.FRONTMATTER, end=MetadataType.INLINE)
case "inline_to_frontmatter":
self.transpose_metadata(begin=MetadataType.INLINE, end=MetadataType.FRONTMATTER)
case "move_to_top":
self.move_inline_metadata(location=InsertLocation.TOP)
case "move_to_after_header":
self.move_inline_metadata(location=InsertLocation.AFTER_TITLE)
case "move_to_bottom":
self.move_inline_metadata(location=InsertLocation.BOTTOM)
case _: # pragma: no cover
return
@@ -453,6 +471,15 @@ class Application:
return
def move_inline_metadata(self, location: InsertLocation) -> None:
"""Move inline metadata to the selected location."""
num_changed = self.vault.move_inline_metadata(location)
if num_changed == 0:
alerts.warning("No notes were changed")
return
alerts.success(f"Moved inline metadata to {location.value} in {num_changed} notes")
def noninteractive_export_csv(self, path: Path) -> None:
"""Export the vault metadata to CSV."""
self._load_vault()

View File

@@ -448,20 +448,27 @@ class Note:
Returns:
bool: Whether the note was updated.
"""
for _k, _v in self.inline_metadata.dict.items():
if re.search(key, _k):
for _value in _v:
if value is None:
if self.inline_metadata.dict != {}:
if key is None:
for _k, _v in self.inline_metadata.dict.items():
for _value in _v:
_k = re.escape(_k)
_value = re.escape(_value)
self.sub(rf"\[?{_k}:: ?{_value}]?", "", is_regex=True)
return True
self.sub(rf"\[?{_k}:: ?\[?\[?{_value}\]?\]?", "", is_regex=True)
return True
if re.search(value, _value):
_k = re.escape(_k)
_value = re.escape(_value)
self.sub(rf"({_k}::) ?{_value}", r"\1", is_regex=True)
return True
for _k, _v in self.inline_metadata.dict.items():
if re.search(key, _k):
for _value in _v:
if value is None:
_k = re.escape(_k)
_value = re.escape(_value)
self.sub(rf"\[?{_k}:: \[?\[?{_value}\]?\]?", "", is_regex=True)
elif re.search(value, _value):
_k = re.escape(_k)
_value = re.escape(_value)
self.sub(rf"\[?({_k}::) ?\[?\[?{_value}\]?\]?", r"\1", is_regex=True)
return True
return False
def write_frontmatter(self, sort_keys: bool = False) -> bool:

View File

@@ -282,7 +282,7 @@ class Questions:
{"name": "Add Metadata", "value": "add_metadata"},
{"name": "Delete Metadata", "value": "delete_metadata"},
{"name": "Rename Metadata", "value": "rename_metadata"},
{"name": "Transpose Metadata", "value": "transpose_metadata"},
{"name": "Reorganize Metadata", "value": "reorganize_metadata"},
questionary.Separator("-------------------------------"),
{"name": "Review Changes", "value": "review_changes"},
{"name": "Commit Changes", "value": "commit_changes"},

View File

@@ -8,6 +8,7 @@ from dataclasses import dataclass
from pathlib import Path
import rich.repr
import typer
from rich import box
from rich.progress import Progress, SpinnerColumn, TextColumn
from rich.prompt import Confirm
@@ -53,8 +54,9 @@ class Vault:
self.insert_location: InsertLocation = self._find_insert_location()
self.dry_run: bool = dry_run
self.backup_path: Path = self.vault_path.parent / f"{self.vault_path.name}.bak"
self.exclude_paths: list[Path] = []
self.metadata = VaultMetadata()
self.exclude_paths: list[Path] = []
for p in config.exclude_paths:
self.exclude_paths.append(Path(self.vault_path / p))
@@ -76,13 +78,16 @@ class Vault:
def __rich_repr__(self) -> rich.repr.Result: # pragma: no cover
"""Define rich representation of Vault."""
yield "vault_path", self.vault_path
yield "dry_run", self.dry_run
yield "backup_path", self.backup_path
yield "num_notes", len(self.all_notes)
yield "num_notes_in_scope", len(self.notes_in_scope)
yield "config", self.config
yield "dry_run", self.dry_run
yield "exclude_paths", self.exclude_paths
yield "filters", self.filters
yield "insert_location", self.insert_location
yield "name", self.name
yield "num_notes_in_scope", len(self.notes_in_scope)
yield "num_notes", len(self.all_notes)
yield "vault_path", self.vault_path
def _filter_notes(self) -> list[Note]:
"""Filter notes by path and metadata using the filters defined in self.filters.
@@ -209,6 +214,7 @@ class Vault:
for _note in self.notes_in_scope:
if _note.add_metadata(area=area, key=key, value=value, location=location):
log.trace(f"Added metadata to {_note.note_path}")
num_changed += 1
if num_changed > 0:
@@ -279,6 +285,7 @@ class Vault:
for _note in self.notes_in_scope:
if _note.delete_inline_tag(tag):
log.trace(f"Deleted tag from {_note.note_path}")
num_changed += 1
if num_changed > 0:
@@ -300,6 +307,7 @@ class Vault:
for _note in self.notes_in_scope:
if _note.delete_metadata(key, value):
log.trace(f"Deleted metadata from {_note.note_path}")
num_changed += 1
if num_changed > 0:
@@ -315,6 +323,9 @@ class Vault:
export_format (str, optional): Export as 'csv' or 'json'. Defaults to "csv".
"""
export_file = Path(path).expanduser().resolve()
if not export_file.parent.exists():
alerts.error(f"Path does not exist: {export_file.parent}")
raise typer.Exit(code=1)
match export_format:
case "csv":
@@ -350,7 +361,7 @@ class Vault:
json.dump(dict_to_dump, f, indent=4, ensure_ascii=False, sort_keys=True)
def get_changed_notes(self) -> list[Note]:
"""Returns a list of notes that have changes.
"""Return a list of notes that have changes.
Returns:
list[Note]: List of notes that have changes.
@@ -386,6 +397,29 @@ class Vault:
table.add_row(str(_n), str(_note.note_path.relative_to(self.vault_path)))
console.print(table)
def move_inline_metadata(self, location: InsertLocation) -> int:
"""Move all inline metadata to the selected location.
Args:
location (InsertLocation): Location to move inline metadata to.
Returns:
int: Number of notes that had inline metadata moved.
"""
num_changed = 0
for _note in self.notes_in_scope:
if _note.write_delete_inline_metadata():
log.trace(f"Deleted inline metadata from {_note.note_path}")
num_changed += 1
_note.write_all_inline_metadata(location)
log.trace(f"Wrote all inline metadata to {_note.note_path}")
if num_changed > 0:
self._rebuild_vault_metadata()
return num_changed
def num_excluded_notes(self) -> int:
"""Count number of excluded notes."""
return len(self.all_notes) - len(self.notes_in_scope)
@@ -404,6 +438,7 @@ class Vault:
for _note in self.notes_in_scope:
if _note.rename_inline_tag(old_tag, new_tag):
log.trace(f"Renamed inline tag in {_note.note_path}")
num_changed += 1
if num_changed > 0:
@@ -412,7 +447,7 @@ class Vault:
return num_changed
def rename_metadata(self, key: str, value_1: str, value_2: str = None) -> int:
"""Renames a key or key-value pair in the note's metadata.
"""Rename a key or key-value pair in the note's metadata.
If no value is provided, will rename an entire key.
@@ -428,6 +463,7 @@ class Vault:
for _note in self.notes_in_scope:
if _note.rename_metadata(key, value_1, value_2):
log.trace(f"Renamed metadata in {_note.note_path}")
num_changed += 1
if num_changed > 0:
@@ -468,6 +504,7 @@ class Vault:
location=location,
):
num_changed += 1
log.trace(f"Transposed metadata in {_note.note_path}")
if num_changed > 0:
self._rebuild_vault_metadata()