Skip to content
This repository has been archived by the owner on Jul 1, 2024. It is now read-only.

Commit

Permalink
refactor: pave the way for non-regex key/value deletions
Browse files Browse the repository at this point in the history
  • Loading branch information
natelandau committed Mar 22, 2023
1 parent 08999cb commit fdb1b8b
Show file tree
Hide file tree
Showing 11 changed files with 154 additions and 129 deletions.
34 changes: 19 additions & 15 deletions src/obsidian_metadata/models/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ def application_delete_metadata(self) -> None:

choices = [
questionary.Separator(),
{"name": "Delete inline tag", "value": "delete_inline_tag"},
{"name": "Delete inline tag", "value": "delete_tag"},
{"name": "Delete key", "value": "delete_key"},
{"name": "Delete value", "value": "delete_value"},
questionary.Separator(),
Expand All @@ -142,8 +142,8 @@ def application_delete_metadata(self) -> None:
self.delete_key()
case "delete_value":
self.delete_value()
case "delete_inline_tag":
self.delete_inline_tag()
case "delete_tag":
self.delete_tag()
case _: # pragma: no cover
return

Expand All @@ -153,7 +153,7 @@ def application_rename_metadata(self) -> None:

choices = [
questionary.Separator(),
{"name": "Rename inline tag", "value": "rename_inline_tag"},
{"name": "Rename inline tag", "value": "rename_tag"},
{"name": "Rename key", "value": "rename_key"},
{"name": "Rename value", "value": "rename_value"},
questionary.Separator(),
Expand All @@ -166,8 +166,8 @@ def application_rename_metadata(self) -> None:
self.rename_key()
case "rename_value":
self.rename_value()
case "rename_inline_tag":
self.rename_inline_tag()
case "rename_tag":
self.rename_tag()
case _: # pragma: no cover
return

Expand Down Expand Up @@ -213,7 +213,7 @@ def application_filter(self) -> None: # noqa: C901,PLR0911,PLR0912
self._load_vault()

case "apply_tag_filter":
tag = self.questions.ask_existing_inline_tag()
tag = self.questions.ask_existing_tag()
if tag is None or not tag:
return

Expand Down Expand Up @@ -482,11 +482,11 @@ def commit_changes(self) -> bool:

return True

def delete_inline_tag(self) -> None:
def delete_tag(self) -> None:
"""Delete an inline tag."""
tag = self.questions.ask_existing_inline_tag(question="Which tag would you like to delete?")
tag = self.questions.ask_existing_tag(question="Which tag would you like to delete?")

num_changed = self.vault.delete_inline_tag(tag)
num_changed = self.vault.delete_tag(tag)
if num_changed == 0:
alerts.warning("No notes were changed")
return
Expand All @@ -502,7 +502,9 @@ def delete_key(self) -> None:
if key_to_delete is None: # pragma: no cover
return

num_changed = self.vault.delete_metadata(key_to_delete)
num_changed = self.vault.delete_metadata(
key=key_to_delete, area=MetadataType.ALL, is_regex=True
)
if num_changed == 0:
alerts.warning(f"No notes found with a key matching: [reverse]{key_to_delete}[/]")
return
Expand All @@ -524,7 +526,9 @@ def delete_value(self) -> None:
if value is None: # pragma: no cover
return

num_changed = self.vault.delete_metadata(key, value)
num_changed = self.vault.delete_metadata(
key=key, value=value, area=MetadataType.ALL, is_regex=True
)
if num_changed == 0:
alerts.warning(f"No notes found matching: {key}: {value}")
return
Expand Down Expand Up @@ -577,17 +581,17 @@ def rename_key(self) -> None:
f"Renamed [reverse]{original_key}[/] to [reverse]{new_key}[/] in {num_changed} notes"
)

def rename_inline_tag(self) -> None:
def rename_tag(self) -> None:
"""Rename an inline tag."""
original_tag = self.questions.ask_existing_inline_tag(question="Which tag to rename?")
original_tag = self.questions.ask_existing_tag(question="Which tag to rename?")
if original_tag is None: # pragma: no cover
return

new_tag = self.questions.ask_new_tag("New tag")
if new_tag is None: # pragma: no cover
return

num_changed = self.vault.rename_inline_tag(original_tag, new_tag)
num_changed = self.vault.rename_tag(original_tag, new_tag)
if num_changed == 0:
alerts.warning("No notes were changed")
return
Expand Down
10 changes: 6 additions & 4 deletions src/obsidian_metadata/models/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,10 +289,11 @@ def contains(self, key: str, value: str = None, is_regex: bool = False) -> bool:
"""
return dict_contains(self.dict, key, value, is_regex)

def delete(self, key: str, value_to_delete: str = None) -> bool:
def delete(self, key: str, value_to_delete: str = None, is_regex: bool = False) -> bool:
"""Delete a value or key in the frontmatter. Regex is supported to allow deleting more than one key or value.
Args:
is_regex (bool, optional): Use regex to check. Defaults to False.
key (str): If no value, key to delete. If value, key containing the value.
value_to_delete (str, optional): Value to delete.
Expand All @@ -303,7 +304,7 @@ def delete(self, key: str, value_to_delete: str = None) -> bool:
dictionary=self.dict,
key=key,
value=value_to_delete,
is_regex=True,
is_regex=is_regex,
)

if new_dict != self.dict:
Expand Down Expand Up @@ -459,10 +460,11 @@ def contains(self, key: str, value: str = None, is_regex: bool = False) -> bool:
"""
return dict_contains(self.dict, key, value, is_regex)

def delete(self, key: str, value_to_delete: str = None) -> bool:
def delete(self, key: str, value_to_delete: str = None, is_regex: bool = False) -> bool:
"""Delete a value or key in the inline metadata. Regex is supported to allow deleting more than one key or value.
Args:
is_regex (bool, optional): If True, key and value are treated as regex. Defaults to False.
key (str): If no value, key to delete. If value, key containing the value.
value_to_delete (str, optional): Value to delete.
Expand All @@ -473,7 +475,7 @@ def delete(self, key: str, value_to_delete: str = None) -> bool:
dictionary=self.dict,
key=key,
value=value_to_delete,
is_regex=True,
is_regex=is_regex,
)

if new_dict != self.dict:
Expand Down
63 changes: 37 additions & 26 deletions src/obsidian_metadata/models/notes.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,9 @@ class Note:
dry_run (bool): Whether to run in dry-run mode.
file_content (str): Total contents of the note file (frontmatter and content).
frontmatter (dict): Frontmatter of the note.
inline_tags (list): List of inline tags in the note.
tags (list): List of inline tags in the note.
inline_metadata (dict): Dictionary of inline metadata in the note.
original_file_content (str): Original contents of the note file (frontmatter and content)
"""

def __init__(self, note_path: Path, dry_run: bool = False) -> None:
Expand All @@ -59,7 +60,7 @@ def __init__(self, note_path: Path, dry_run: bool = False) -> None:
alerts.error(f"Note {self.note_path} has invalid frontmatter.\n{e}")
raise typer.Exit(code=1) from e

self.inline_tags: InlineTags = InlineTags(self.file_content)
self.tags: InlineTags = InlineTags(self.file_content)
self.inline_metadata: InlineMetadata = InlineMetadata(self.file_content)
self.original_file_content: str = self.file_content

Expand All @@ -68,7 +69,7 @@ def __rich_repr__(self) -> rich.repr.Result: # pragma: no cover
yield "note_path", self.note_path
yield "dry_run", self.dry_run
yield "frontmatter", self.frontmatter
yield "inline_tags", self.inline_tags
yield "tags", self.tags
yield "inline_metadata", self.inline_metadata

def add_metadata( # noqa: C901
Expand Down Expand Up @@ -114,8 +115,8 @@ def add_metadata( # noqa: C901
case MetadataType.TAGS:
new_values = []
if isinstance(value, list):
new_values = [_v for _v in value if self.inline_tags.add(_v)]
elif self.inline_tags.add(value):
new_values = [_v for _v in value if self.tags.add(_v)]
elif self.tags.add(value):
new_values = [value]

if new_values:
Expand Down Expand Up @@ -153,7 +154,7 @@ def commit(self, path: Path = None) -> None:
alerts.error(f"Note {p} not found. Exiting")
raise typer.Exit(code=1) from e

def contains_inline_tag(self, tag: str, is_regex: bool = False) -> bool:
def contains_tag(self, tag: str, is_regex: bool = False) -> bool:
"""Check if a note contains the specified inline tag.
Args:
Expand All @@ -163,7 +164,7 @@ def contains_inline_tag(self, tag: str, is_regex: bool = False) -> bool:
Returns:
bool: Whether the note has inline tags.
"""
return self.inline_tags.contains(tag, is_regex=is_regex)
return self.tags.contains(tag, is_regex=is_regex)

def contains_metadata(self, key: str, value: str = None, is_regex: bool = False) -> bool:
"""Check if a note has a key or a key-value pair in its Frontmatter or InlineMetadata.
Expand Down Expand Up @@ -195,45 +196,50 @@ def delete_all_metadata(self) -> None:
for key in self.inline_metadata.dict:
self.delete_metadata(key=key, area=MetadataType.INLINE)

for tag in self.inline_tags.list:
self.delete_inline_tag(tag=tag)
for tag in self.tags.list:
self.delete_tag(tag=tag)

self.frontmatter.delete_all()
self.write_frontmatter()

def delete_inline_tag(self, tag: str) -> bool:
"""Delete an inline tag from the `inline_tags` attribute AND removes the tag from the text of the note if it exists.
def delete_tag(self, tag: str) -> bool:
"""Delete an inline tag from the `tags` attribute AND removes the tag from the text of the note if it exists.
Args:
tag (str): Tag to delete.
Returns:
bool: Whether the tag was deleted.
"""
new_list = self.inline_tags.list.copy()
new_list = self.tags.list.copy()

for _t in new_list:
if re.search(tag, _t):
_t = re.escape(_t)
self.sub(rf"#{_t}([ \|,;:\*\(\)\[\]\\\.\n#&])", r"\1", is_regex=True)
self.inline_tags.delete(tag)
self.tags.delete(tag)

if new_list != self.inline_tags.list:
if new_list != self.tags.list:
return True

return False

def delete_metadata(
self, key: str, value: str = None, area: MetadataType = MetadataType.ALL
self,
key: str,
value: str = None,
area: MetadataType = MetadataType.ALL,
is_regex: bool = False,
) -> bool:
"""Delete a key or key-value pair from the note's Metadata object and the content of the note. Regex is supported.
If no value is provided, will delete an entire specified key.
Args:
area (MetadataType, optional): Area to delete metadata from. Defaults to MetadataType.ALL.
is_regex (bool, optional): Whether to use regex to match the key/value.
key (str): Key to delete.
value (str, optional): Value to delete.
area (MetadataType, optional): Area to delete metadata from. Defaults to MetadataType.ALL.
Returns:
bool: Whether the key or key-value pair was deleted.
Expand All @@ -242,15 +248,15 @@ def delete_metadata(

if (
area == MetadataType.FRONTMATTER or area == MetadataType.ALL
) and self.frontmatter.delete(key, value):
) and self.frontmatter.delete(key=key, value_to_delete=value, is_regex=is_regex):
self.write_frontmatter()
changed_value = True

if (
area == MetadataType.INLINE or area == MetadataType.ALL
) and self.inline_metadata.contains(key, value):
self.write_delete_inline_metadata(key, value)
self.inline_metadata.delete(key, value)
self.write_delete_inline_metadata(key=key, value=value, is_regex=is_regex)
self.inline_metadata.delete(key=key, value_to_delete=value, is_regex=is_regex)
changed_value = True

if changed_value:
Expand All @@ -266,7 +272,7 @@ def has_changes(self) -> bool:
if self.frontmatter.has_changes():
return True

if self.inline_tags.has_changes():
if self.tags.has_changes():
return True

if self.inline_metadata.has_changes():
Expand Down Expand Up @@ -298,7 +304,7 @@ def print_note(self) -> None:
"""Print the note to the console."""
console.print(self.file_content)

def rename_inline_tag(self, tag_1: str, tag_2: str) -> bool:
def rename_tag(self, tag_1: str, tag_2: str) -> bool:
"""Rename an inline tag. Updates the Metadata object and the text of the note.
Args:
Expand All @@ -308,13 +314,13 @@ def rename_inline_tag(self, tag_1: str, tag_2: str) -> bool:
Returns:
bool: Whether the tag was renamed.
"""
if tag_1 in self.inline_tags.list:
if tag_1 in self.tags.list:
self.sub(
rf"#{tag_1}([ \|,;:\*\(\)\[\]\\\.\n#&])",
rf"#{tag_2}\1",
is_regex=True,
)
self.inline_tags.rename(tag_1, tag_2)
self.tags.rename(tag_1, tag_2)
return True
return False

Expand Down Expand Up @@ -447,12 +453,15 @@ def transpose_metadata( # noqa: C901, PLR0912, PLR0911

return False

def write_delete_inline_metadata(self, key: str = None, value: str = None) -> bool:
def write_delete_inline_metadata(
self, key: str = None, value: str = None, is_regex: bool = False
) -> bool:
"""For a given inline metadata key and/or key-value pair, delete it from the text of the note. If no key is provided, will delete all inline metadata from the text of the note.
IMPORTANT: This method makes no changes to the InlineMetadata object.
Args:
is_regex (bool, optional): Whether the key is a regex pattern or plain text. Defaults to False.
key (str, optional): Key to delete.
value (str, optional): Value to delete.
Expand All @@ -469,13 +478,15 @@ def write_delete_inline_metadata(self, key: str = None, value: str = None) -> bo
return True

for _k, _v in self.inline_metadata.dict.items():
if re.search(key, _k):
if (is_regex and re.search(key, _k)) or (not is_regex and 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):
elif (is_regex and re.search(value, _value)) or (
not is_regex and value == _value
):
_k = re.escape(_k)
_value = re.escape(_value)
self.sub(rf"\[?({_k}::) ?\[?\[?{_value}\]?\]?", r"\1", is_regex=True)
Expand Down
6 changes: 3 additions & 3 deletions src/obsidian_metadata/models/questions.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ def __init__(self, vault: Vault = None, key: str = None) -> None:
self.vault = vault
self.key = key

def _validate_existing_inline_tag(self, text: str) -> bool | str:
def _validate_existing_tag(self, text: str) -> bool | str:
"""Validate an existing inline tag.
Returns:
Expand Down Expand Up @@ -344,11 +344,11 @@ def ask_confirm(self, question: str, default: bool = True) -> bool: # pragma: n
question, default=default, style=self.style, qmark="INPUT |"
).ask()

def ask_existing_inline_tag(self, question: str = "Enter a tag") -> str: # pragma: no cover
def ask_existing_tag(self, question: str = "Enter a tag") -> str: # pragma: no cover
"""Ask the user for an existing inline tag."""
return questionary.text(
question,
validate=self._validate_existing_inline_tag,
validate=self._validate_existing_tag,
style=self.style,
qmark="INPUT |",
).ask()
Expand Down
Loading

0 comments on commit fdb1b8b

Please sign in to comment.