feat: add new tags (#16)

This commit is contained in:
Nathaniel Landau
2023-02-04 23:32:55 -05:00
committed by Nathaniel Landau
parent 17985615b3
commit d94d9f2197
10 changed files with 574 additions and 495 deletions

View File

@@ -45,46 +45,45 @@ Once installed, run `obsidian-metadata` in your terminal to enter an interactive
**Inspect Metadata**
- View all metadata in the vault
- View all metadata in the vault
- View all frontmatter
- View all inline metadata
- View all inline tags
- Export all metadata to CSV or JSON file
- **View all metadata in the vault**
- View all **frontmatter**
- View all **inline metadata**
- View all **inline tags**
- **Export all metadata to CSV or JSON file**
**Filter Notes in Scope**: Limit the scope of notes to be processed with one or more filters.
- Path filter (regex): Limit scope based on the path or filename
- Metadata Filter: Limit scope based on a key or key/value pair
- Tag Filter: Limit scope based on an in-text tag
- List and Clear Filters List all current filters and clear one or all
- List notes in scope: List notes that will be processed.
- **Path filter (regex)**: Limit scope based on the path or filename
- **Metadata filter**: Limit scope based on a key or key/value pair
- **Tag filter**: Limit scope based on an in-text tag
- **List and clear filters**: List all current filters and clear one or all
- **List notes in scope**: List notes that will be processed.
**Add Metadata**: Add new metadata to your vault.
- Add metadata to the frontmatter
- Add to inline metadata - Set `insert_location` in the config to control where the new metadata is inserted. (Default: Bottom)
- Add to inline tag (Not yet implemented)
- **Add new metadata to the frontmatter**
- **Add new inline metadata** - Set `insert_location` in the config to control where the new metadata is inserted. (Default: Bottom)
- **Add new inline tag** - Set `insert_location` in the config to control where the new tag is inserted. (Default: Bottom)
**Rename Metadata**: Rename either a key and all associated values, a specific value within a key. or an in-text tag.
- Rename a key
- Rename a value
- rename an inline tag
- **Rename a key**
- **Rename a value**
- **Rename an inline tag**
**Delete Metadata**: Delete either a key and all associated values, or a specific value.
- Delete a key and associated values
- Delete a value from a key
- Delete an inline tag
- **Delete a key and associated values**
- **Delete a value from a key**
- **Delete an inline tag**
**Review Changes**: Prior to committing changes, review all changes that will be made.
- View a diff of the changes that will be made
- **View a diff of the changes** that will be made
**Commit Changes**: Write the changes to disk. This step is not undoable.
- Commit changes to the vault
- **Commit changes to the vault**
### Configuration

View File

@@ -110,17 +110,18 @@ def main(
[bold underline]Filter Notes in Scope[/]
Limit the scope of notes to be processed with one or more filters.
• Path filter (regex): Limit scope based on the path or filename
• Metadata Filter: Limit scope based on a key or key/value pair
• Tag Filter: Limit scope based on an in-text tag
• List and Clear Filters: List all current filters and clear one or all
• Metadata filter: Limit scope based on a key or key/value pair
• Tag filter: Limit scope based on an in-text tag
• List and clear filters: List all current filters and clear one or all
• List notes in scope: List notes that will be processed.
[bold underline]Add Metadata[/]
Add new metadata to your vault.
• Add metadata to the frontmatter
• Add to inline metadata - Set `insert_location` in the config to
• Add new metadata to the frontmatter
• Add new inline metadata - Set `insert_location` in the config to
control where the new metadata is inserted. (Default: Bottom)
[dim]Add to inline tag (Not yet implemented)[/]
• Add new inline tag - Set `insert_location` in the config to
control where the new tag is inserted. (Default: Bottom)
[bold underline]Rename Metadata[/]
Rename either a key and all associated values, a specific value within a key. or an in-text tag.

View File

@@ -104,7 +104,19 @@ class Application:
alerts.success(f"Added metadata to {num_changed} notes")
case MetadataType.TAGS:
alerts.warning(f"Adding metadata to {area} is not supported yet")
tag = self.questions.ask_new_tag()
if tag is None: # pragma: no cover
return
num_changed = self.vault.add_metadata(
area=area, value=tag, location=self.vault.insert_location
)
if num_changed == 0: # pragma: no cover
alerts.warning(f"No notes were changed")
return
alerts.success(f"Added metadata to {num_changed} notes")
case _: # pragma: no cover
return

View File

@@ -401,7 +401,7 @@ class InlineMetadata:
"""
return f"InlineMetadata(inline_metadata={self.dict})"
def add(self, key: str, value: str | list[str] = None) -> bool:
def add(self, key: str, value: str = None) -> bool:
"""Add a key and value to the inline metadata.
Args:
@@ -411,15 +411,12 @@ class InlineMetadata:
Returns:
bool: True if the metadata was added
"""
if value is None:
if value is None or value == "" or value == "None":
if key not in self.dict:
self.dict[key] = []
return True
return False
if isinstance(value, list):
value = value[0]
if key not in self.dict:
self.dict[key] = [value]
return True
@@ -564,6 +561,23 @@ class InlineTags:
)
)
def add(self, new_tag: str) -> bool:
"""Add a new inline tag.
Args:
new_tag (str): Tag to add.
Returns:
bool: True if a tag was added.
"""
if new_tag in self.list:
return False
new_list = self.list.copy()
new_list.append(new_tag)
self.list = sorted(new_list)
return True
def contains(self, tag: str, is_regex: bool = False) -> bool:
"""Check if a tag exists in the metadata.

View File

@@ -120,7 +120,7 @@ class Note:
def add_metadata(
self,
area: MetadataType,
key: str,
key: str = None,
value: str | list[str] = None,
location: InsertLocation = None,
) -> bool:
@@ -128,7 +128,7 @@ class Note:
Args:
area (MetadataType): Area to add metadata to.
key (str): Key to add.
key (str, optional): Key to add
location (InsertLocation, optional): Location to add inline metadata and tags.
value (str, optional): Value to add.
@@ -140,7 +140,7 @@ class Note:
return True
try:
if area is MetadataType.INLINE and self.inline_metadata.add(key, value):
if area is MetadataType.INLINE and self.inline_metadata.add(key, str(value)):
line = f"{key}:: " if value is None else f"{key}:: {value}"
self.insert(new_string=line, location=location)
return True
@@ -149,9 +149,10 @@ class Note:
log.warning(f"Could not add metadata to {self.note_path}: {e}")
return False
if area is MetadataType.TAGS:
# TODO: implement adding to intext tags
pass
if area is MetadataType.TAGS and self.inline_tags.add(str(value)):
line = f"#{value}"
self.insert(new_string=line, location=location)
return True
return False

View File

@@ -436,8 +436,12 @@ class Questions:
question, validate=self._validate_new_key, style=self.style, qmark="INPUT |"
).ask()
def ask_new_tag(self, question: str = "New tag name") -> str: # pragma: no cover
"""Ask the user for a new inline tag."""
def ask_new_tag(self, question: str = "Enter a new tag") -> str: # pragma: no cover
"""Ask the user for a new tag.
Args:
question (str, optional): The question to ask. Defaults to "Enter a new tag".
"""
return questionary.text(
question, validate=self._validate_new_tag, style=self.style, qmark="INPUT |"
).ask()

View File

@@ -165,7 +165,7 @@ class Vault:
def add_metadata(
self,
area: MetadataType,
key: str,
key: str = None,
value: str | list[str] = None,
location: InsertLocation = None,
) -> int:
@@ -186,7 +186,7 @@ class Vault:
num_changed = 0
for _note in self.notes_in_scope:
if _note.add_metadata(area, key, value, location):
if _note.add_metadata(area=area, key=key, value=value, location=location):
num_changed += 1
if num_changed > 0:

View File

@@ -42,7 +42,7 @@ def test_abort(test_application, mocker, capsys) -> None:
assert "Done!" in captured.out
def test_add_metadata_frontmatter_success(test_application, mocker, capsys) -> None:
def test_add_metadata_frontmatter(test_application, mocker, capsys) -> None:
"""Test adding new metadata to the vault."""
app = test_application
app._load_vault()
@@ -69,7 +69,7 @@ def test_add_metadata_frontmatter_success(test_application, mocker, capsys) -> N
assert captured.out == Regex(r"SUCCESS +\| Added metadata to.*\d+.*notes", re.DOTALL)
def test_add_metadata_inline_success(test_application, mocker, capsys) -> None:
def test_add_metadata_inline(test_application, mocker, capsys) -> None:
"""Test adding new metadata to the vault."""
app = test_application
app._load_vault()
@@ -96,6 +96,29 @@ def test_add_metadata_inline_success(test_application, mocker, capsys) -> None:
assert captured.out == Regex(r"SUCCESS +\| Added metadata to.*\d+.*notes", re.DOTALL)
def test_add_metadata_tag(test_application, mocker, capsys) -> None:
"""Test adding new metadata to the vault."""
app = test_application
app._load_vault()
mocker.patch(
"obsidian_metadata.models.application.Questions.ask_application_main",
side_effect=["add_metadata", KeyError],
)
mocker.patch(
"obsidian_metadata.models.application.Questions.ask_area",
return_value=MetadataType.TAGS,
)
mocker.patch(
"obsidian_metadata.models.application.Questions.ask_new_tag",
return_value="new_tag",
)
with pytest.raises(KeyError):
app.application_main()
captured = capsys.readouterr()
assert captured.out == Regex(r"SUCCESS +\| Added metadata to.*\d+.*notes", re.DOTALL)
def test_delete_inline_tag(test_application, mocker, capsys) -> None:
"""Test renaming an inline tag."""
app = test_application

View File

@@ -69,6 +69,455 @@ repeated_key:: repeated_key_value2
"""
def test_frontmatter_create() -> None:
"""Test frontmatter creation."""
frontmatter = Frontmatter(INLINE_CONTENT)
assert frontmatter.dict == {}
frontmatter = Frontmatter(FRONTMATTER_CONTENT)
assert frontmatter.dict == {
"frontmatter_Key1": ["frontmatter_Key1_value"],
"frontmatter_Key2": ["article", "note"],
"shared_key1": ["shared_key1_value"],
"tags": ["tag_1", "tag_2", "📅/tag_3"],
}
assert frontmatter.dict_original == {
"frontmatter_Key1": ["frontmatter_Key1_value"],
"frontmatter_Key2": ["article", "note"],
"shared_key1": ["shared_key1_value"],
"tags": ["tag_1", "tag_2", "📅/tag_3"],
}
def test_frontmatter_contains() -> None:
"""Test frontmatter contains."""
frontmatter = Frontmatter(FRONTMATTER_CONTENT)
assert frontmatter.contains("frontmatter_Key1") is True
assert frontmatter.contains("frontmatter_Key2", "article") is True
assert frontmatter.contains("frontmatter_Key3") is False
assert frontmatter.contains("frontmatter_Key2", "no value") is False
assert frontmatter.contains(r"\d$", is_regex=True) is True
assert frontmatter.contains(r"^\d", is_regex=True) is False
assert frontmatter.contains("key", r"_\d", is_regex=True) is False
assert frontmatter.contains("key", r"\w\d_", is_regex=True) is True
def test_frontmatter_add() -> None:
"""Test frontmatter add."""
frontmatter = Frontmatter(FRONTMATTER_CONTENT)
assert frontmatter.add("frontmatter_Key1") is False
assert frontmatter.add("added_key") is True
assert frontmatter.dict == {
"added_key": [],
"frontmatter_Key1": ["frontmatter_Key1_value"],
"frontmatter_Key2": ["article", "note"],
"shared_key1": ["shared_key1_value"],
"tags": ["tag_1", "tag_2", "📅/tag_3"],
}
assert frontmatter.add("added_key", "added_value") is True
assert frontmatter.dict == {
"added_key": ["added_value"],
"frontmatter_Key1": ["frontmatter_Key1_value"],
"frontmatter_Key2": ["article", "note"],
"shared_key1": ["shared_key1_value"],
"tags": ["tag_1", "tag_2", "📅/tag_3"],
}
assert frontmatter.add("added_key", "added_value_2") is True
assert frontmatter.dict == {
"added_key": ["added_value", "added_value_2"],
"frontmatter_Key1": ["frontmatter_Key1_value"],
"frontmatter_Key2": ["article", "note"],
"shared_key1": ["shared_key1_value"],
"tags": ["tag_1", "tag_2", "📅/tag_3"],
}
assert frontmatter.add("added_key", ["added_value_3", "added_value_4"]) is True
assert frontmatter.dict == {
"added_key": ["added_value", "added_value_2", "added_value_3", "added_value_4"],
"frontmatter_Key1": ["frontmatter_Key1_value"],
"frontmatter_Key2": ["article", "note"],
"shared_key1": ["shared_key1_value"],
"tags": ["tag_1", "tag_2", "📅/tag_3"],
}
assert frontmatter.add("added_key2", ["added_value_1", "added_value_2"]) is True
assert frontmatter.dict == {
"added_key": ["added_value", "added_value_2", "added_value_3", "added_value_4"],
"added_key2": ["added_value_1", "added_value_2"],
"frontmatter_Key1": ["frontmatter_Key1_value"],
"frontmatter_Key2": ["article", "note"],
"shared_key1": ["shared_key1_value"],
"tags": ["tag_1", "tag_2", "📅/tag_3"],
}
assert frontmatter.add("added_key3", "added_value_1") is True
assert frontmatter.dict == {
"added_key": ["added_value", "added_value_2", "added_value_3", "added_value_4"],
"added_key2": ["added_value_1", "added_value_2"],
"added_key3": ["added_value_1"],
"frontmatter_Key1": ["frontmatter_Key1_value"],
"frontmatter_Key2": ["article", "note"],
"shared_key1": ["shared_key1_value"],
"tags": ["tag_1", "tag_2", "📅/tag_3"],
}
assert frontmatter.add("added_key3", "added_value_1") is False
def test_frontmatter_rename() -> None:
"""Test frontmatter rename."""
frontmatter = Frontmatter(FRONTMATTER_CONTENT)
assert frontmatter.dict == {
"frontmatter_Key1": ["frontmatter_Key1_value"],
"frontmatter_Key2": ["article", "note"],
"shared_key1": ["shared_key1_value"],
"tags": ["tag_1", "tag_2", "📅/tag_3"],
}
assert frontmatter.rename("no key", "new key") is False
assert frontmatter.rename("tags", "no tag", "new key") is False
assert frontmatter.has_changes() is False
assert frontmatter.rename("tags", "tag_2", "new tag") is True
assert frontmatter.dict["tags"] == ["new tag", "tag_1", "📅/tag_3"]
assert frontmatter.rename("tags", "old_tags") is True
assert frontmatter.dict["old_tags"] == ["new tag", "tag_1", "📅/tag_3"]
assert "tags" not in frontmatter.dict
assert frontmatter.has_changes() is True
def test_frontmatter_delete() -> None:
"""Test Frontmatter delete method."""
frontmatter = Frontmatter(FRONTMATTER_CONTENT)
assert frontmatter.dict == {
"frontmatter_Key1": ["frontmatter_Key1_value"],
"frontmatter_Key2": ["article", "note"],
"shared_key1": ["shared_key1_value"],
"tags": ["tag_1", "tag_2", "📅/tag_3"],
}
assert frontmatter.delete("no key") is False
assert frontmatter.delete("tags", "no value") is False
assert frontmatter.delete(r"\d{3}") is False
assert frontmatter.has_changes() is False
assert frontmatter.delete("tags", "tag_2") is True
assert frontmatter.dict["tags"] == ["tag_1", "📅/tag_3"]
assert frontmatter.delete("tags") is True
assert "tags" not in frontmatter.dict
assert frontmatter.has_changes() is True
assert frontmatter.delete("shared_key1", r"\w+") is True
assert frontmatter.dict["shared_key1"] == []
assert frontmatter.delete(r"\w.tter") is True
assert frontmatter.dict == {"shared_key1": []}
def test_frontmatter_yaml_conversion():
"""Test Frontmatter to_yaml method."""
new_frontmatter: str = """\
tags:
- tag_1
- tag_2
- 📅/tag_3
frontmatter_Key1: frontmatter_Key1_value
frontmatter_Key2:
- article
- note
shared_key1: shared_key1_value
"""
new_frontmatter_sorted: str = """\
frontmatter_Key1: frontmatter_Key1_value
frontmatter_Key2:
- article
- note
shared_key1: shared_key1_value
tags:
- tag_1
- tag_2
- 📅/tag_3
"""
frontmatter = Frontmatter(FRONTMATTER_CONTENT)
assert frontmatter.to_yaml() == new_frontmatter
assert frontmatter.to_yaml(sort_keys=True) == new_frontmatter_sorted
def test_inline_metadata_add() -> None:
"""Test inline add."""
inline = InlineMetadata(INLINE_CONTENT)
assert inline.add("bold_key1") is False
assert inline.add("bold_key1", "bold_key1_value") is False
assert inline.add("added_key") is True
assert inline.dict == {
"added_key": [],
"bold_key1": ["bold_key1_value"],
"bold_key2": ["bold_key2_value"],
"emoji_📅_key": ["emoji_📅_key_value"],
"in_text_key1": ["in_text_key1_value"],
"in_text_key2": ["in_text_key2_value"],
"link_key": ["link_key_value"],
"repeated_key": ["repeated_key_value1", "repeated_key_value2"],
"tag_key": ["tag_key_value"],
}
assert inline.add("added_key1", "added_value") is True
assert inline.dict == {
"added_key": [],
"added_key1": ["added_value"],
"bold_key1": ["bold_key1_value"],
"bold_key2": ["bold_key2_value"],
"emoji_📅_key": ["emoji_📅_key_value"],
"in_text_key1": ["in_text_key1_value"],
"in_text_key2": ["in_text_key2_value"],
"link_key": ["link_key_value"],
"repeated_key": ["repeated_key_value1", "repeated_key_value2"],
"tag_key": ["tag_key_value"],
}
with pytest.raises(ValueError):
assert inline.add("added_key1", "added_value_2") is True
assert inline.dict == {
"added_key": [],
"added_key1": ["added_value"],
"bold_key1": ["bold_key1_value"],
"bold_key2": ["bold_key2_value"],
"emoji_📅_key": ["emoji_📅_key_value"],
"in_text_key1": ["in_text_key1_value"],
"in_text_key2": ["in_text_key2_value"],
"link_key": ["link_key_value"],
"repeated_key": ["repeated_key_value1", "repeated_key_value2"],
"tag_key": ["tag_key_value"],
}
assert inline.add("added_key", "added_value")
assert inline.dict == {
"added_key": ["added_value"],
"added_key1": ["added_value"],
"bold_key1": ["bold_key1_value"],
"bold_key2": ["bold_key2_value"],
"emoji_📅_key": ["emoji_📅_key_value"],
"in_text_key1": ["in_text_key1_value"],
"in_text_key2": ["in_text_key2_value"],
"link_key": ["link_key_value"],
"repeated_key": ["repeated_key_value1", "repeated_key_value2"],
"tag_key": ["tag_key_value"],
}
def test_inline_metadata_contains() -> None:
"""Test inline metadata contains method."""
inline = InlineMetadata(INLINE_CONTENT)
assert inline.contains("bold_key1") is True
assert inline.contains("bold_key2", "bold_key2_value") is True
assert inline.contains("bold_key3") is False
assert inline.contains("bold_key2", "no value") is False
assert inline.contains(r"\w{4}_key", is_regex=True) is True
assert inline.contains(r"^\d", is_regex=True) is False
assert inline.contains("1$", r"\d_value", is_regex=True) is True
assert inline.contains("key", r"^\d_value", is_regex=True) is False
def test_inline_metadata_create() -> None:
"""Test inline metadata creation."""
inline = InlineMetadata(FRONTMATTER_CONTENT)
assert inline.dict == {}
inline = InlineMetadata(INLINE_CONTENT)
assert inline.dict == {
"bold_key1": ["bold_key1_value"],
"bold_key2": ["bold_key2_value"],
"emoji_📅_key": ["emoji_📅_key_value"],
"in_text_key1": ["in_text_key1_value"],
"in_text_key2": ["in_text_key2_value"],
"link_key": ["link_key_value"],
"repeated_key": ["repeated_key_value1", "repeated_key_value2"],
"tag_key": ["tag_key_value"],
}
assert inline.dict_original == {
"bold_key1": ["bold_key1_value"],
"bold_key2": ["bold_key2_value"],
"emoji_📅_key": ["emoji_📅_key_value"],
"in_text_key1": ["in_text_key1_value"],
"in_text_key2": ["in_text_key2_value"],
"link_key": ["link_key_value"],
"repeated_key": ["repeated_key_value1", "repeated_key_value2"],
"tag_key": ["tag_key_value"],
}
def test_inline_metadata_delete() -> None:
"""Test inline metadata delete."""
inline = InlineMetadata(INLINE_CONTENT)
assert inline.dict == {
"bold_key1": ["bold_key1_value"],
"bold_key2": ["bold_key2_value"],
"emoji_📅_key": ["emoji_📅_key_value"],
"in_text_key1": ["in_text_key1_value"],
"in_text_key2": ["in_text_key2_value"],
"link_key": ["link_key_value"],
"repeated_key": ["repeated_key_value1", "repeated_key_value2"],
"tag_key": ["tag_key_value"],
}
assert inline.delete("no key") is False
assert inline.delete("repeated_key", "no value") is False
assert inline.has_changes() is False
assert inline.delete("repeated_key", "repeated_key_value1") is True
assert inline.dict["repeated_key"] == ["repeated_key_value2"]
assert inline.delete("repeated_key") is True
assert "repeated_key" not in inline.dict
assert inline.has_changes() is True
assert inline.delete(r"\d{3}") is False
assert inline.delete(r"bold_key\d") is True
assert inline.dict == {
"emoji_📅_key": ["emoji_📅_key_value"],
"in_text_key1": ["in_text_key1_value"],
"in_text_key2": ["in_text_key2_value"],
"link_key": ["link_key_value"],
"tag_key": ["tag_key_value"],
}
assert inline.delete("emoji_📅_key", ".*📅.*") is True
assert inline.dict == {
"emoji_📅_key": [],
"in_text_key1": ["in_text_key1_value"],
"in_text_key2": ["in_text_key2_value"],
"link_key": ["link_key_value"],
"tag_key": ["tag_key_value"],
}
def test_inline_metadata_rename() -> None:
"""Test inline metadata rename."""
inline = InlineMetadata(INLINE_CONTENT)
assert inline.dict == {
"bold_key1": ["bold_key1_value"],
"bold_key2": ["bold_key2_value"],
"emoji_📅_key": ["emoji_📅_key_value"],
"in_text_key1": ["in_text_key1_value"],
"in_text_key2": ["in_text_key2_value"],
"link_key": ["link_key_value"],
"repeated_key": ["repeated_key_value1", "repeated_key_value2"],
"tag_key": ["tag_key_value"],
}
assert inline.rename("no key", "new key") is False
assert inline.rename("repeated_key", "no value", "new key") is False
assert inline.has_changes() is False
assert inline.rename("repeated_key", "repeated_key_value1", "new value") is True
assert inline.dict["repeated_key"] == ["new value", "repeated_key_value2"]
assert inline.rename("repeated_key", "old_key") is True
assert inline.dict["old_key"] == ["new value", "repeated_key_value2"]
assert "repeated_key" not in inline.dict
assert inline.has_changes() is True
def test_inline_tags_add() -> None:
"""Test inline tags add."""
tags = InlineTags(INLINE_CONTENT)
assert tags.add("bold_tag") is False
assert tags.add("new_tag") is True
assert tags.list == [
"bold_tag",
"in_text_tag",
"inline_tag_top1",
"inline_tag_top2",
"new_tag",
"tag_key_value",
]
def test_inline_tags_contains() -> None:
"""Test inline tags contains."""
tags = InlineTags(INLINE_CONTENT)
assert tags.contains("bold_tag") is True
assert tags.contains("no tag") is False
assert tags.contains(r"\w_\w", is_regex=True) is True
assert tags.contains(r"\d_\d", is_regex=True) is False
def test_inline_tags_create() -> None:
"""Test inline tags creation."""
tags = InlineTags(FRONTMATTER_CONTENT)
tags.metadata_key
assert tags.list == []
tags = InlineTags(INLINE_CONTENT)
assert tags.list == [
"bold_tag",
"in_text_tag",
"inline_tag_top1",
"inline_tag_top2",
"tag_key_value",
]
assert tags.list_original == [
"bold_tag",
"in_text_tag",
"inline_tag_top1",
"inline_tag_top2",
"tag_key_value",
]
def test_inline_tags_delete() -> None:
"""Test inline tags delete."""
tags = InlineTags(INLINE_CONTENT)
assert tags.list == [
"bold_tag",
"in_text_tag",
"inline_tag_top1",
"inline_tag_top2",
"tag_key_value",
]
assert tags.delete("no tag") is False
assert tags.has_changes() is False
assert tags.delete("bold_tag") is True
assert tags.list == [
"in_text_tag",
"inline_tag_top1",
"inline_tag_top2",
"tag_key_value",
]
assert tags.has_changes() is True
assert tags.delete(r"\d{3}") is False
assert tags.delete(r"inline_tag_top\d") is True
assert tags.list == ["in_text_tag", "tag_key_value"]
def test_inline_tags_rename() -> None:
"""Test inline tags rename."""
tags = InlineTags(INLINE_CONTENT)
assert tags.list == [
"bold_tag",
"in_text_tag",
"inline_tag_top1",
"inline_tag_top2",
"tag_key_value",
]
assert tags.rename("no tag", "new tag") is False
assert tags.has_changes() is False
assert tags.rename("bold_tag", "new tag") is True
assert tags.list == [
"in_text_tag",
"inline_tag_top1",
"inline_tag_top2",
"new tag",
"tag_key_value",
]
assert tags.has_changes() is True
def test_vault_metadata() -> None:
"""Test VaultMetadata class."""
vm = VaultMetadata()
@@ -295,452 +744,3 @@ def test_vault_metadata_rename() -> None:
assert vm.rename("tags", "old_tags") is True
assert vm.dict["old_tags"] == ["new tag", "tag 1", "tag 3"]
assert "tags" not in vm.dict
def test_frontmatter_create() -> None:
"""Test frontmatter creation."""
frontmatter = Frontmatter(INLINE_CONTENT)
assert frontmatter.dict == {}
frontmatter = Frontmatter(FRONTMATTER_CONTENT)
assert frontmatter.dict == {
"frontmatter_Key1": ["frontmatter_Key1_value"],
"frontmatter_Key2": ["article", "note"],
"shared_key1": ["shared_key1_value"],
"tags": ["tag_1", "tag_2", "📅/tag_3"],
}
assert frontmatter.dict_original == {
"frontmatter_Key1": ["frontmatter_Key1_value"],
"frontmatter_Key2": ["article", "note"],
"shared_key1": ["shared_key1_value"],
"tags": ["tag_1", "tag_2", "📅/tag_3"],
}
def test_frontmatter_contains() -> None:
"""Test frontmatter contains."""
frontmatter = Frontmatter(FRONTMATTER_CONTENT)
assert frontmatter.contains("frontmatter_Key1") is True
assert frontmatter.contains("frontmatter_Key2", "article") is True
assert frontmatter.contains("frontmatter_Key3") is False
assert frontmatter.contains("frontmatter_Key2", "no value") is False
assert frontmatter.contains(r"\d$", is_regex=True) is True
assert frontmatter.contains(r"^\d", is_regex=True) is False
assert frontmatter.contains("key", r"_\d", is_regex=True) is False
assert frontmatter.contains("key", r"\w\d_", is_regex=True) is True
def test_frontmatter_add() -> None:
"""Test frontmatter add."""
frontmatter = Frontmatter(FRONTMATTER_CONTENT)
assert frontmatter.add("frontmatter_Key1") is False
assert frontmatter.add("added_key") is True
assert frontmatter.dict == {
"added_key": [],
"frontmatter_Key1": ["frontmatter_Key1_value"],
"frontmatter_Key2": ["article", "note"],
"shared_key1": ["shared_key1_value"],
"tags": ["tag_1", "tag_2", "📅/tag_3"],
}
assert frontmatter.add("added_key", "added_value") is True
assert frontmatter.dict == {
"added_key": ["added_value"],
"frontmatter_Key1": ["frontmatter_Key1_value"],
"frontmatter_Key2": ["article", "note"],
"shared_key1": ["shared_key1_value"],
"tags": ["tag_1", "tag_2", "📅/tag_3"],
}
assert frontmatter.add("added_key", "added_value_2") is True
assert frontmatter.dict == {
"added_key": ["added_value", "added_value_2"],
"frontmatter_Key1": ["frontmatter_Key1_value"],
"frontmatter_Key2": ["article", "note"],
"shared_key1": ["shared_key1_value"],
"tags": ["tag_1", "tag_2", "📅/tag_3"],
}
assert frontmatter.add("added_key", ["added_value_3", "added_value_4"]) is True
assert frontmatter.dict == {
"added_key": ["added_value", "added_value_2", "added_value_3", "added_value_4"],
"frontmatter_Key1": ["frontmatter_Key1_value"],
"frontmatter_Key2": ["article", "note"],
"shared_key1": ["shared_key1_value"],
"tags": ["tag_1", "tag_2", "📅/tag_3"],
}
assert frontmatter.add("added_key2", ["added_value_1", "added_value_2"]) is True
assert frontmatter.dict == {
"added_key": ["added_value", "added_value_2", "added_value_3", "added_value_4"],
"added_key2": ["added_value_1", "added_value_2"],
"frontmatter_Key1": ["frontmatter_Key1_value"],
"frontmatter_Key2": ["article", "note"],
"shared_key1": ["shared_key1_value"],
"tags": ["tag_1", "tag_2", "📅/tag_3"],
}
assert frontmatter.add("added_key3", "added_value_1") is True
assert frontmatter.dict == {
"added_key": ["added_value", "added_value_2", "added_value_3", "added_value_4"],
"added_key2": ["added_value_1", "added_value_2"],
"added_key3": ["added_value_1"],
"frontmatter_Key1": ["frontmatter_Key1_value"],
"frontmatter_Key2": ["article", "note"],
"shared_key1": ["shared_key1_value"],
"tags": ["tag_1", "tag_2", "📅/tag_3"],
}
assert frontmatter.add("added_key3", "added_value_1") is False
def test_frontmatter_rename() -> None:
"""Test frontmatter rename."""
frontmatter = Frontmatter(FRONTMATTER_CONTENT)
assert frontmatter.dict == {
"frontmatter_Key1": ["frontmatter_Key1_value"],
"frontmatter_Key2": ["article", "note"],
"shared_key1": ["shared_key1_value"],
"tags": ["tag_1", "tag_2", "📅/tag_3"],
}
assert frontmatter.rename("no key", "new key") is False
assert frontmatter.rename("tags", "no tag", "new key") is False
assert frontmatter.has_changes() is False
assert frontmatter.rename("tags", "tag_2", "new tag") is True
assert frontmatter.dict["tags"] == ["new tag", "tag_1", "📅/tag_3"]
assert frontmatter.rename("tags", "old_tags") is True
assert frontmatter.dict["old_tags"] == ["new tag", "tag_1", "📅/tag_3"]
assert "tags" not in frontmatter.dict
assert frontmatter.has_changes() is True
def test_frontmatter_delete() -> None:
"""Test Frontmatter delete method."""
frontmatter = Frontmatter(FRONTMATTER_CONTENT)
assert frontmatter.dict == {
"frontmatter_Key1": ["frontmatter_Key1_value"],
"frontmatter_Key2": ["article", "note"],
"shared_key1": ["shared_key1_value"],
"tags": ["tag_1", "tag_2", "📅/tag_3"],
}
assert frontmatter.delete("no key") is False
assert frontmatter.delete("tags", "no value") is False
assert frontmatter.delete(r"\d{3}") is False
assert frontmatter.has_changes() is False
assert frontmatter.delete("tags", "tag_2") is True
assert frontmatter.dict["tags"] == ["tag_1", "📅/tag_3"]
assert frontmatter.delete("tags") is True
assert "tags" not in frontmatter.dict
assert frontmatter.has_changes() is True
assert frontmatter.delete("shared_key1", r"\w+") is True
assert frontmatter.dict["shared_key1"] == []
assert frontmatter.delete(r"\w.tter") is True
assert frontmatter.dict == {"shared_key1": []}
def test_frontmatter_yaml_conversion():
"""Test Frontmatter to_yaml method."""
new_frontmatter: str = """\
tags:
- tag_1
- tag_2
- 📅/tag_3
frontmatter_Key1: frontmatter_Key1_value
frontmatter_Key2:
- article
- note
shared_key1: shared_key1_value
"""
new_frontmatter_sorted: str = """\
frontmatter_Key1: frontmatter_Key1_value
frontmatter_Key2:
- article
- note
shared_key1: shared_key1_value
tags:
- tag_1
- tag_2
- 📅/tag_3
"""
frontmatter = Frontmatter(FRONTMATTER_CONTENT)
assert frontmatter.to_yaml() == new_frontmatter
assert frontmatter.to_yaml(sort_keys=True) == new_frontmatter_sorted
def test_inline_metadata_create() -> None:
"""Test inline metadata creation."""
inline = InlineMetadata(FRONTMATTER_CONTENT)
assert inline.dict == {}
inline = InlineMetadata(INLINE_CONTENT)
assert inline.dict == {
"bold_key1": ["bold_key1_value"],
"bold_key2": ["bold_key2_value"],
"emoji_📅_key": ["emoji_📅_key_value"],
"in_text_key1": ["in_text_key1_value"],
"in_text_key2": ["in_text_key2_value"],
"link_key": ["link_key_value"],
"repeated_key": ["repeated_key_value1", "repeated_key_value2"],
"tag_key": ["tag_key_value"],
}
assert inline.dict_original == {
"bold_key1": ["bold_key1_value"],
"bold_key2": ["bold_key2_value"],
"emoji_📅_key": ["emoji_📅_key_value"],
"in_text_key1": ["in_text_key1_value"],
"in_text_key2": ["in_text_key2_value"],
"link_key": ["link_key_value"],
"repeated_key": ["repeated_key_value1", "repeated_key_value2"],
"tag_key": ["tag_key_value"],
}
def test_inline_contains() -> None:
"""Test inline metadata contains method."""
inline = InlineMetadata(INLINE_CONTENT)
assert inline.contains("bold_key1") is True
assert inline.contains("bold_key2", "bold_key2_value") is True
assert inline.contains("bold_key3") is False
assert inline.contains("bold_key2", "no value") is False
assert inline.contains(r"\w{4}_key", is_regex=True) is True
assert inline.contains(r"^\d", is_regex=True) is False
assert inline.contains("1$", r"\d_value", is_regex=True) is True
assert inline.contains("key", r"^\d_value", is_regex=True) is False
def test_inline_add() -> None:
"""Test inline add."""
inline = InlineMetadata(INLINE_CONTENT)
assert inline.add("bold_key1") is False
assert inline.add("bold_key1", "bold_key1_value") is False
assert inline.add("added_key") is True
assert inline.dict == {
"added_key": [],
"bold_key1": ["bold_key1_value"],
"bold_key2": ["bold_key2_value"],
"emoji_📅_key": ["emoji_📅_key_value"],
"in_text_key1": ["in_text_key1_value"],
"in_text_key2": ["in_text_key2_value"],
"link_key": ["link_key_value"],
"repeated_key": ["repeated_key_value1", "repeated_key_value2"],
"tag_key": ["tag_key_value"],
}
assert inline.add("added_key1", "added_value") is True
assert inline.dict == {
"added_key": [],
"added_key1": ["added_value"],
"bold_key1": ["bold_key1_value"],
"bold_key2": ["bold_key2_value"],
"emoji_📅_key": ["emoji_📅_key_value"],
"in_text_key1": ["in_text_key1_value"],
"in_text_key2": ["in_text_key2_value"],
"link_key": ["link_key_value"],
"repeated_key": ["repeated_key_value1", "repeated_key_value2"],
"tag_key": ["tag_key_value"],
}
with pytest.raises(ValueError):
assert inline.add("added_key1", "added_value_2") is True
assert inline.dict == {
"added_key": [],
"added_key1": ["added_value"],
"bold_key1": ["bold_key1_value"],
"bold_key2": ["bold_key2_value"],
"emoji_📅_key": ["emoji_📅_key_value"],
"in_text_key1": ["in_text_key1_value"],
"in_text_key2": ["in_text_key2_value"],
"link_key": ["link_key_value"],
"repeated_key": ["repeated_key_value1", "repeated_key_value2"],
"tag_key": ["tag_key_value"],
}
assert inline.add("added_key2", ["added_value_1", "added_value_2"]) is True
assert inline.dict == {
"added_key": [],
"added_key1": ["added_value"],
"added_key2": ["added_value_1"],
"bold_key1": ["bold_key1_value"],
"bold_key2": ["bold_key2_value"],
"emoji_📅_key": ["emoji_📅_key_value"],
"in_text_key1": ["in_text_key1_value"],
"in_text_key2": ["in_text_key2_value"],
"link_key": ["link_key_value"],
"repeated_key": ["repeated_key_value1", "repeated_key_value2"],
"tag_key": ["tag_key_value"],
}
assert inline.add("added_key", "added_value")
assert inline.dict == {
"added_key": ["added_value"],
"added_key1": ["added_value"],
"added_key2": ["added_value_1"],
"bold_key1": ["bold_key1_value"],
"bold_key2": ["bold_key2_value"],
"emoji_📅_key": ["emoji_📅_key_value"],
"in_text_key1": ["in_text_key1_value"],
"in_text_key2": ["in_text_key2_value"],
"link_key": ["link_key_value"],
"repeated_key": ["repeated_key_value1", "repeated_key_value2"],
"tag_key": ["tag_key_value"],
}
def test_inline_metadata_rename() -> None:
"""Test inline metadata rename."""
inline = InlineMetadata(INLINE_CONTENT)
assert inline.dict == {
"bold_key1": ["bold_key1_value"],
"bold_key2": ["bold_key2_value"],
"emoji_📅_key": ["emoji_📅_key_value"],
"in_text_key1": ["in_text_key1_value"],
"in_text_key2": ["in_text_key2_value"],
"link_key": ["link_key_value"],
"repeated_key": ["repeated_key_value1", "repeated_key_value2"],
"tag_key": ["tag_key_value"],
}
assert inline.rename("no key", "new key") is False
assert inline.rename("repeated_key", "no value", "new key") is False
assert inline.has_changes() is False
assert inline.rename("repeated_key", "repeated_key_value1", "new value") is True
assert inline.dict["repeated_key"] == ["new value", "repeated_key_value2"]
assert inline.rename("repeated_key", "old_key") is True
assert inline.dict["old_key"] == ["new value", "repeated_key_value2"]
assert "repeated_key" not in inline.dict
assert inline.has_changes() is True
def test_inline_metadata_delete() -> None:
"""Test inline metadata delete."""
inline = InlineMetadata(INLINE_CONTENT)
assert inline.dict == {
"bold_key1": ["bold_key1_value"],
"bold_key2": ["bold_key2_value"],
"emoji_📅_key": ["emoji_📅_key_value"],
"in_text_key1": ["in_text_key1_value"],
"in_text_key2": ["in_text_key2_value"],
"link_key": ["link_key_value"],
"repeated_key": ["repeated_key_value1", "repeated_key_value2"],
"tag_key": ["tag_key_value"],
}
assert inline.delete("no key") is False
assert inline.delete("repeated_key", "no value") is False
assert inline.has_changes() is False
assert inline.delete("repeated_key", "repeated_key_value1") is True
assert inline.dict["repeated_key"] == ["repeated_key_value2"]
assert inline.delete("repeated_key") is True
assert "repeated_key" not in inline.dict
assert inline.has_changes() is True
assert inline.delete(r"\d{3}") is False
assert inline.delete(r"bold_key\d") is True
assert inline.dict == {
"emoji_📅_key": ["emoji_📅_key_value"],
"in_text_key1": ["in_text_key1_value"],
"in_text_key2": ["in_text_key2_value"],
"link_key": ["link_key_value"],
"tag_key": ["tag_key_value"],
}
assert inline.delete("emoji_📅_key", ".*📅.*") is True
assert inline.dict == {
"emoji_📅_key": [],
"in_text_key1": ["in_text_key1_value"],
"in_text_key2": ["in_text_key2_value"],
"link_key": ["link_key_value"],
"tag_key": ["tag_key_value"],
}
def test_inline_tags_create() -> None:
"""Test inline tags creation."""
tags = InlineTags(FRONTMATTER_CONTENT)
tags.metadata_key
assert tags.list == []
tags = InlineTags(INLINE_CONTENT)
assert tags.list == [
"bold_tag",
"in_text_tag",
"inline_tag_top1",
"inline_tag_top2",
"tag_key_value",
]
assert tags.list_original == [
"bold_tag",
"in_text_tag",
"inline_tag_top1",
"inline_tag_top2",
"tag_key_value",
]
def test_inline_tags_contains() -> None:
"""Test inline tags contains."""
tags = InlineTags(INLINE_CONTENT)
assert tags.contains("bold_tag") is True
assert tags.contains("no tag") is False
assert tags.contains(r"\w_\w", is_regex=True) is True
assert tags.contains(r"\d_\d", is_regex=True) is False
def test_inline_tags_rename() -> None:
"""Test inline tags rename."""
tags = InlineTags(INLINE_CONTENT)
assert tags.list == [
"bold_tag",
"in_text_tag",
"inline_tag_top1",
"inline_tag_top2",
"tag_key_value",
]
assert tags.rename("no tag", "new tag") is False
assert tags.has_changes() is False
assert tags.rename("bold_tag", "new tag") is True
assert tags.list == [
"in_text_tag",
"inline_tag_top1",
"inline_tag_top2",
"new tag",
"tag_key_value",
]
assert tags.has_changes() is True
def test_inline_tags_delete() -> None:
"""Test inline tags delete."""
tags = InlineTags(INLINE_CONTENT)
assert tags.list == [
"bold_tag",
"in_text_tag",
"inline_tag_top1",
"inline_tag_top2",
"tag_key_value",
]
assert tags.delete("no tag") is False
assert tags.has_changes() is False
assert tags.delete("bold_tag") is True
assert tags.list == [
"in_text_tag",
"inline_tag_top1",
"inline_tag_top2",
"tag_key_value",
]
assert tags.has_changes() is True
assert tags.delete(r"\d{3}") is False
assert tags.delete(r"inline_tag_top\d") is True
assert tags.list == ["in_text_tag", "tag_key_value"]

View File

@@ -103,6 +103,7 @@ def test_add_metadata_inline(short_note) -> None:
def test_add_metadata_frontmatter(sample_note) -> None:
"""Test adding metadata."""
note = Note(note_path=sample_note)
assert note.add_metadata(MetadataType.FRONTMATTER, "frontmatter_Key1") is False
assert note.add_metadata(MetadataType.FRONTMATTER, "shared_key1", "shared_key1_value") is False
assert note.add_metadata(MetadataType.FRONTMATTER, "new_key1") is True
@@ -159,6 +160,30 @@ def test_add_metadata_frontmatter(sample_note) -> None:
}
def test_add_metadata_tag(sample_note) -> None:
"""Test adding inline tags."""
note = Note(note_path=sample_note)
assert (
note.add_metadata(MetadataType.TAGS, value="shared_tag", location=InsertLocation.TOP)
is False
)
assert (
note.add_metadata(MetadataType.TAGS, value="a_new_tag", location=InsertLocation.TOP) is True
)
assert note.inline_tags.list == [
"a_new_tag",
"inline_tag_bottom1",
"inline_tag_bottom2",
"inline_tag_top1",
"inline_tag_top2",
"intext_tag1",
"intext_tag2",
"shared_tag",
]
assert "#a_new_tag" in note.file_content
def test_contains_inline_tag(sample_note) -> None:
"""Test contains inline tag."""
note = Note(note_path=sample_note)