mirror of
https://github.com/natelandau/obsidian-metadata.git
synced 2025-11-17 09:23:40 -05:00
feat(application): add new metadata to frontmatter (#9)
* feat(frontmatter): frontmatter method to add key, values * build: add pysnooper to aid in debugging * feat(application): add new frontmatter * build: clean up dev container * fix(notes): diff now pretty prints in a table * docs(readme): update usage information * docs(readme): fix markdown lists
This commit is contained in:
@@ -5,12 +5,13 @@ from typing import Any
|
||||
|
||||
import questionary
|
||||
from rich import print
|
||||
|
||||
from textwrap import dedent
|
||||
from obsidian_metadata._config import VaultConfig
|
||||
from obsidian_metadata._utils.alerts import logger as log
|
||||
from obsidian_metadata.models import Patterns, Vault
|
||||
from obsidian_metadata._utils import alerts
|
||||
from obsidian_metadata.models.questions import Questions
|
||||
from obsidian_metadata.models.enums import MetadataType
|
||||
|
||||
PATTERNS = Patterns()
|
||||
|
||||
@@ -38,102 +39,224 @@ class Application:
|
||||
log.info(f"Indexed {self.vault.num_notes()} notes from {self.vault.vault_path}")
|
||||
self.questions = Questions(vault=self.vault)
|
||||
|
||||
def main_app(self) -> None:
|
||||
def application_main(self) -> None:
|
||||
"""Questions for the main application."""
|
||||
self.load_vault()
|
||||
|
||||
while True:
|
||||
print("\n")
|
||||
self.vault.info()
|
||||
|
||||
match self.questions.ask_main_application(): # noqa: E999
|
||||
case None:
|
||||
break
|
||||
match self.questions.ask_application_main(): # noqa: E999
|
||||
case "vault_actions":
|
||||
self.application_vault()
|
||||
case "inspect_metadata":
|
||||
self.application_inspect_metadata()
|
||||
case "filter_notes":
|
||||
self.load_vault(path_filter=self.questions.ask_for_filter_path())
|
||||
case "all_metadata":
|
||||
self.vault.metadata.print_metadata()
|
||||
case "backup_vault":
|
||||
self.vault.backup()
|
||||
case "delete_backup":
|
||||
self.vault.delete_backup()
|
||||
case "list_notes":
|
||||
self.vault.list_editable_notes()
|
||||
case "rename_inline_tag":
|
||||
self.rename_inline_tag()
|
||||
case "delete_inline_tag":
|
||||
self.delete_inline_tag()
|
||||
case "rename_key":
|
||||
self.rename_key()
|
||||
case "delete_key":
|
||||
self.delete_key()
|
||||
case "rename_value":
|
||||
self.rename_value()
|
||||
case "delete_value":
|
||||
self.delete_value()
|
||||
self.application_filter()
|
||||
case "add_metadata":
|
||||
self.application_add_metadata()
|
||||
case "rename_metadata":
|
||||
self.application_rename_metadata()
|
||||
case "delete_metadata":
|
||||
self.application_delete_metadata()
|
||||
case "review_changes":
|
||||
self.review_changes()
|
||||
case "commit_changes":
|
||||
if self.commit_changes():
|
||||
break
|
||||
|
||||
log.error("Commit failed. Please run with -vvv for more info.")
|
||||
break
|
||||
|
||||
case "abort":
|
||||
case _:
|
||||
break
|
||||
|
||||
print("Done!")
|
||||
return
|
||||
|
||||
def rename_key(self) -> None:
|
||||
"""Renames a key in the vault."""
|
||||
def application_add_metadata(self) -> None:
|
||||
"""Add metadata."""
|
||||
help_text = """
|
||||
[bold underline]Add Metadata[/]
|
||||
Add new metadata to your vault. Currently only supports
|
||||
adding to the frontmatter of a note.\n
|
||||
"""
|
||||
print(dedent(help_text))
|
||||
|
||||
original_key = self.questions.ask_for_existing_key(
|
||||
question="Which key would you like to rename?"
|
||||
)
|
||||
if original_key is None:
|
||||
return
|
||||
area = self.questions.ask_area()
|
||||
match area:
|
||||
case MetadataType.FRONTMATTER:
|
||||
key = self.questions.ask_new_key(question="Enter the key for the new metadata")
|
||||
if key is None:
|
||||
return
|
||||
|
||||
new_key = self.questions.ask_for_new_key()
|
||||
if new_key is None:
|
||||
return
|
||||
value = self.questions.ask_new_value(
|
||||
question="Enter the value for the new metadata"
|
||||
)
|
||||
if value is None:
|
||||
return
|
||||
|
||||
num_changed = self.vault.rename_metadata(original_key, new_key)
|
||||
if num_changed == 0:
|
||||
alerts.warning(f"No notes were changed")
|
||||
return
|
||||
num_changed = self.vault.add_metadata(area, key, value)
|
||||
if num_changed == 0:
|
||||
alerts.warning(f"No notes were changed")
|
||||
return
|
||||
|
||||
alerts.success(
|
||||
f"Renamed [reverse]{original_key}[/] to [reverse]{new_key}[/] in {num_changed} notes"
|
||||
)
|
||||
alerts.success(f"Added metadata to {num_changed} notes")
|
||||
|
||||
def rename_inline_tag(self) -> None:
|
||||
"""Rename an inline tag."""
|
||||
case MetadataType.INLINE:
|
||||
alerts.warning(f"Adding metadata to {area} is not supported yet")
|
||||
|
||||
original_tag = self.questions.ask_for_existing_inline_tag(question="Which tag to rename?")
|
||||
if original_tag is None:
|
||||
return
|
||||
case MetadataType.TAGS:
|
||||
alerts.warning(f"Adding metadata to {area} is not supported yet")
|
||||
|
||||
new_tag = self.questions.ask_for_new_tag("New tag")
|
||||
if new_tag is None:
|
||||
return
|
||||
case _:
|
||||
return
|
||||
|
||||
num_changed = self.vault.rename_inline_tag(original_tag, new_tag)
|
||||
if num_changed == 0:
|
||||
alerts.warning(f"No notes were changed")
|
||||
return
|
||||
def application_filter(self) -> None:
|
||||
"""Filter notes."""
|
||||
help_text = """
|
||||
[bold underline]Filter Notes[/]
|
||||
Enter a regex to filter notes by path. This allows you to
|
||||
specify a subset of notes to update. Leave empty to include
|
||||
all markdown files.\n
|
||||
"""
|
||||
print(dedent(help_text))
|
||||
|
||||
alerts.success(
|
||||
f"Renamed [reverse]{original_tag}[/] to [reverse]{new_tag}[/] in {num_changed} notes"
|
||||
)
|
||||
return
|
||||
choices = [
|
||||
{"name": "Apply regex filter", "value": "apply_filter"},
|
||||
{"name": "List notes in scope", "value": "list_notes"},
|
||||
questionary.Separator(),
|
||||
{"name": "Back", "value": "back"},
|
||||
]
|
||||
while True:
|
||||
match self.questions.ask_selection(choices=choices, question="Select an action"):
|
||||
case "apply_filter":
|
||||
|
||||
path_filter = self.questions.ask_filter_path()
|
||||
if path_filter is None:
|
||||
return
|
||||
|
||||
if path_filter == "":
|
||||
path_filter = None
|
||||
|
||||
self.load_vault(path_filter=path_filter)
|
||||
|
||||
total_notes = self.vault.num_notes() + self.vault.num_excluded_notes()
|
||||
|
||||
if path_filter is None:
|
||||
alerts.success(f"Loaded all {total_notes} total notes")
|
||||
else:
|
||||
alerts.success(
|
||||
f"Loaded {self.vault.num_notes()} notes from {total_notes} total notes"
|
||||
)
|
||||
|
||||
case "list_notes":
|
||||
self.vault.list_editable_notes()
|
||||
print("\n")
|
||||
|
||||
case _:
|
||||
return
|
||||
|
||||
def application_inspect_metadata(self) -> None:
|
||||
"""View metadata."""
|
||||
help_text = """
|
||||
[bold underline]View Metadata[/]
|
||||
Inspect the metadata in your vault. Note, uncommitted changes will be reflected in these reports\n
|
||||
"""
|
||||
print(dedent(help_text))
|
||||
|
||||
choices = [
|
||||
{"name": "View all metadata", "value": "all_metadata"},
|
||||
questionary.Separator(),
|
||||
{"name": "Back", "value": "back"},
|
||||
]
|
||||
while True:
|
||||
match self.questions.ask_selection(choices=choices, question="Select a vault action"):
|
||||
case "all_metadata":
|
||||
self.vault.metadata.print_metadata()
|
||||
case _:
|
||||
return
|
||||
|
||||
def application_vault(self) -> None:
|
||||
"""Vault actions."""
|
||||
help_text = """
|
||||
[bold underline]Vault Actions[/]
|
||||
Create or delete a backup of your vault.\n
|
||||
"""
|
||||
print(dedent(help_text))
|
||||
|
||||
choices = [
|
||||
{"name": "Backup vault", "value": "backup_vault"},
|
||||
{"name": "Delete vault backup", "value": "delete_backup"},
|
||||
questionary.Separator(),
|
||||
{"name": "Back", "value": "back"},
|
||||
]
|
||||
|
||||
while True:
|
||||
match self.questions.ask_selection(choices=choices, question="Select a vault action"):
|
||||
case "backup_vault":
|
||||
self.vault.backup()
|
||||
case "delete_backup":
|
||||
self.vault.delete_backup()
|
||||
case _:
|
||||
return
|
||||
|
||||
def application_delete_metadata(self) -> None:
|
||||
help_text = """
|
||||
[bold underline]Delete Metadata[/]
|
||||
Delete either a key and all associated values, or a specific value.\n
|
||||
"""
|
||||
print(dedent(help_text))
|
||||
|
||||
choices = [
|
||||
{"name": "Delete key", "value": "delete_key"},
|
||||
{"name": "Delete value", "value": "delete_value"},
|
||||
{"name": "Delete inline tag", "value": "delete_inline_tag"},
|
||||
questionary.Separator(),
|
||||
{"name": "Back", "value": "back"},
|
||||
]
|
||||
match self.questions.ask_selection(
|
||||
choices=choices, question="Select a metadata type to delete"
|
||||
):
|
||||
case "delete_key":
|
||||
self.delete_key()
|
||||
case "delete_value":
|
||||
self.delete_value()
|
||||
case "delete_inline_tag":
|
||||
self.delete_inline_tag()
|
||||
case _:
|
||||
return
|
||||
|
||||
def application_rename_metadata(self) -> None:
|
||||
"""Rename metadata."""
|
||||
help_text = """
|
||||
[bold underline]Rename Metadata[/]\n
|
||||
Select the type of metadata to rename.\n
|
||||
"""
|
||||
print(dedent(help_text))
|
||||
|
||||
choices = [
|
||||
{"name": "Rename key", "value": "rename_key"},
|
||||
{"name": "Rename value", "value": "rename_value"},
|
||||
{"name": "Rename inline tag", "value": "rename_inline_tag"},
|
||||
questionary.Separator(),
|
||||
{"name": "Back", "value": "back"},
|
||||
]
|
||||
match self.questions.ask_selection(
|
||||
choices=choices, question="Select a metadata type to rename"
|
||||
):
|
||||
case "rename_key":
|
||||
self.rename_key()
|
||||
case "rename_value":
|
||||
self.rename_value()
|
||||
case "rename_inline_tag":
|
||||
self.rename_inline_tag()
|
||||
case _:
|
||||
return
|
||||
|
||||
###########################################################################
|
||||
def delete_inline_tag(self) -> None:
|
||||
"""Delete an inline tag."""
|
||||
tag = self.questions.ask_for_existing_inline_tag(
|
||||
question="Which tag would you like to delete?"
|
||||
)
|
||||
tag = self.questions.ask_existing_inline_tag(question="Which tag would you like to delete?")
|
||||
|
||||
num_changed = self.vault.delete_inline_tag(tag)
|
||||
if num_changed == 0:
|
||||
@@ -145,7 +268,7 @@ class Application:
|
||||
|
||||
def delete_key(self) -> None:
|
||||
"""Delete a key from the vault."""
|
||||
key_to_delete = self.questions.ask_for_existing_keys_regex(
|
||||
key_to_delete = self.questions.ask_existing_keys_regex(
|
||||
question="Regex for the key(s) you'd like to delete?"
|
||||
)
|
||||
if key_to_delete is None:
|
||||
@@ -162,42 +285,14 @@ class Application:
|
||||
|
||||
return
|
||||
|
||||
def rename_value(self) -> None:
|
||||
"""Rename a value in the vault."""
|
||||
key = self.questions.ask_for_existing_key(
|
||||
question="Which key contains the value to rename?"
|
||||
)
|
||||
if key is None:
|
||||
return
|
||||
|
||||
question_key = Questions(vault=self.vault, key=key)
|
||||
value = question_key.ask_for_existing_value(
|
||||
question="Which value would you like to rename?"
|
||||
)
|
||||
if value is None:
|
||||
return
|
||||
|
||||
new_value = question_key.ask_for_new_value()
|
||||
if new_value is None:
|
||||
return
|
||||
|
||||
num_changes = self.vault.rename_metadata(key, value, new_value)
|
||||
if num_changes == 0:
|
||||
alerts.warning(f"No notes were changed")
|
||||
return
|
||||
|
||||
alerts.success(f"Renamed '{key}:{value}' to '{key}:{new_value}' in {num_changes} notes")
|
||||
|
||||
def delete_value(self) -> None:
|
||||
"""Delete a value from the vault."""
|
||||
key = self.questions.ask_for_existing_key(
|
||||
question="Which key contains the value to delete?"
|
||||
)
|
||||
key = self.questions.ask_existing_key(question="Which key contains the value to delete?")
|
||||
if key is None:
|
||||
return
|
||||
|
||||
questions2 = Questions(vault=self.vault, key=key)
|
||||
value = questions2.ask_for_existing_value_regex(question="Regex for the value to delete")
|
||||
value = questions2.ask_existing_value_regex(question="Regex for the value to delete")
|
||||
if value is None:
|
||||
return
|
||||
|
||||
@@ -212,6 +307,71 @@ class Application:
|
||||
|
||||
return
|
||||
|
||||
def rename_key(self) -> None:
|
||||
"""Renames a key in the vault."""
|
||||
|
||||
original_key = self.questions.ask_existing_key(
|
||||
question="Which key would you like to rename?"
|
||||
)
|
||||
if original_key is None:
|
||||
return
|
||||
|
||||
new_key = self.questions.ask_new_key()
|
||||
if new_key is None:
|
||||
return
|
||||
|
||||
num_changed = self.vault.rename_metadata(original_key, new_key)
|
||||
if num_changed == 0:
|
||||
alerts.warning(f"No notes were changed")
|
||||
return
|
||||
|
||||
alerts.success(
|
||||
f"Renamed [reverse]{original_key}[/] to [reverse]{new_key}[/] in {num_changed} notes"
|
||||
)
|
||||
|
||||
def rename_inline_tag(self) -> None:
|
||||
"""Rename an inline tag."""
|
||||
|
||||
original_tag = self.questions.ask_existing_inline_tag(question="Which tag to rename?")
|
||||
if original_tag is None:
|
||||
return
|
||||
|
||||
new_tag = self.questions.ask_new_tag("New tag")
|
||||
if new_tag is None:
|
||||
return
|
||||
|
||||
num_changed = self.vault.rename_inline_tag(original_tag, new_tag)
|
||||
if num_changed == 0:
|
||||
alerts.warning(f"No notes were changed")
|
||||
return
|
||||
|
||||
alerts.success(
|
||||
f"Renamed [reverse]{original_tag}[/] to [reverse]{new_tag}[/] in {num_changed} notes"
|
||||
)
|
||||
return
|
||||
|
||||
def rename_value(self) -> None:
|
||||
"""Rename a value in the vault."""
|
||||
key = self.questions.ask_existing_key(question="Which key contains the value to rename?")
|
||||
if key is None:
|
||||
return
|
||||
|
||||
question_key = Questions(vault=self.vault, key=key)
|
||||
value = question_key.ask_existing_value(question="Which value would you like to rename?")
|
||||
if value is None:
|
||||
return
|
||||
|
||||
new_value = question_key.ask_new_value()
|
||||
if new_value is None:
|
||||
return
|
||||
|
||||
num_changes = self.vault.rename_metadata(key, value, new_value)
|
||||
if num_changes == 0:
|
||||
alerts.warning(f"No notes were changed")
|
||||
return
|
||||
|
||||
alerts.success(f"Renamed '{key}:{value}' to '{key}:{new_value}' in {num_changes} notes")
|
||||
|
||||
def review_changes(self) -> None:
|
||||
"""Review all changes in the vault."""
|
||||
changed_notes = self.vault.get_changed_notes()
|
||||
@@ -239,7 +399,7 @@ class Application:
|
||||
choices.append({"name": "Return", "value": "return"})
|
||||
|
||||
while True:
|
||||
note_to_review = self.questions.ask_for_selection(
|
||||
note_to_review = self.questions.ask_selection(
|
||||
choices=choices,
|
||||
question="Select a new to view the diff",
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user