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

feat: add new tags #16

Merged
merged 2 commits into from
Feb 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 21 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
13 changes: 7 additions & 6 deletions src/obsidian_metadata/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
14 changes: 13 additions & 1 deletion src/obsidian_metadata/models/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,19 @@ def application_add_metadata(self) -> None:
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

Expand Down
24 changes: 19 additions & 5 deletions src/obsidian_metadata/models/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ def __repr__(self) -> str: # pragma: no cover
"""
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:
Expand All @@ -411,15 +411,12 @@ def add(self, key: str, value: str | list[str] = None) -> bool:
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
Expand Down Expand Up @@ -564,6 +561,23 @@ def _grab_inline_tags(self, file_content: str) -> list[str]:
)
)

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.

Expand Down
13 changes: 7 additions & 6 deletions src/obsidian_metadata/models/notes.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,15 +120,15 @@ def _rename_inline_metadata(self, key: str, value_1: str, value_2: str = None) -
def add_metadata(
self,
area: MetadataType,
key: str,
key: str = None,
value: str | list[str] = None,
location: InsertLocation = None,
) -> bool:
"""Add metadata to the note if it does not already exist.

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.

Expand All @@ -140,7 +140,7 @@ def add_metadata(
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
Expand All @@ -149,9 +149,10 @@ def add_metadata(
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

Expand Down
8 changes: 6 additions & 2 deletions src/obsidian_metadata/models/questions.py
Original file line number Diff line number Diff line change
Expand Up @@ -436,8 +436,12 @@ def ask_new_key(self, question: str = "New key name") -> str: # pragma: no cove
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()
Expand Down
4 changes: 2 additions & 2 deletions src/obsidian_metadata/models/vault.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ def _rebuild_vault_metadata(self) -> None:
def add_metadata(
self,
area: MetadataType,
key: str,
key: str = None,
value: str | list[str] = None,
location: InsertLocation = None,
) -> int:
Expand All @@ -186,7 +186,7 @@ def add_metadata(
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:
Expand Down
27 changes: 25 additions & 2 deletions tests/application_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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()
Expand All @@ -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
Expand Down
Loading