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

Commit

Permalink
feat: add new tags (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
natelandau committed Feb 5, 2023
1 parent 1798561 commit d94d9f2
Show file tree
Hide file tree
Showing 10 changed files with 451 additions and 372 deletions.
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

0 comments on commit d94d9f2

Please sign in to comment.