fix(application): improve ux (#10)

* fix(vault): use table for listing notes in scope

* style: alphabetize methods

* fix(application): subcommand usage text formatting

* fix(questions): improve question style
This commit is contained in:
Nathaniel Landau
2023-01-30 13:29:18 -05:00
committed by GitHub
parent 48174ebde9
commit c0d37eff3b
7 changed files with 298 additions and 267 deletions

View File

@@ -29,16 +29,6 @@ class Application:
self.dry_run = dry_run
self.questions = Questions()
def load_vault(self, path_filter: str = None) -> None:
"""Load the vault.
Args:
path_filter (str, optional): Regex to filter notes by path.
"""
self.vault: Vault = Vault(config=self.config, dry_run=self.dry_run, path_filter=path_filter)
log.info(f"Indexed {self.vault.num_notes()} notes from {self.vault.vault_path}")
self.questions = Questions(vault=self.vault)
def application_main(self) -> None:
"""Questions for the main application."""
self.load_vault()
@@ -76,9 +66,9 @@ class Application:
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
USAGE | Add Metadata
[dim]Add new metadata to your vault. Currently only supports
adding to the frontmatter of a note.[/]
"""
print(dedent(help_text))
@@ -114,10 +104,10 @@ class Application:
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
USAGE | Filter Notes
[dim]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
all markdown files.[/]
"""
print(dedent(help_text))
@@ -151,7 +141,6 @@ class Application:
case "list_notes":
self.vault.list_editable_notes()
print("\n")
case _:
return
@@ -159,8 +148,9 @@ class Application:
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
USAGE | View Metadata
[dim]Inspect the metadata in your vault. Note, uncommitted
changes will be reflected in these reports[/]
"""
print(dedent(help_text))
@@ -179,8 +169,8 @@ class Application:
def application_vault(self) -> None:
"""Vault actions."""
help_text = """
[bold underline]Vault Actions[/]
Create or delete a backup of your vault.\n
USAGE | Vault Actions
[dim]Create or delete a backup of your vault.[/]
"""
print(dedent(help_text))
@@ -202,8 +192,9 @@ class Application:
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
USAGE | Delete Metadata
[dim]Delete either a key and all associated values,
or a specific value.[/]
"""
print(dedent(help_text))
@@ -229,8 +220,8 @@ class Application:
def application_rename_metadata(self) -> None:
"""Rename metadata."""
help_text = """
[bold underline]Rename Metadata[/]\n
Select the type of metadata to rename.\n
USAGE | Rename Metadata
[dim]Select the type of metadata to rename.[/]
"""
print(dedent(help_text))
@@ -253,7 +244,33 @@ class Application:
case _:
return
###########################################################################
def commit_changes(self) -> bool:
"""Write all changes to disk.
Returns:
True if changes were committed, False otherwise.
"""
changed_notes = self.vault.get_changed_notes()
if len(changed_notes) == 0:
print("\n")
alerts.notice("No changes to commit.\n")
return False
backup = questionary.confirm("Create backup before committing changes").ask()
if backup is None:
return False
if backup:
self.vault.backup()
if questionary.confirm(f"Commit {len(changed_notes)} changed files to disk?").ask():
self.vault.write()
alerts.success(f"{len(changed_notes)} changes committed to disk. Exiting")
return True
return False
def delete_inline_tag(self) -> None:
"""Delete an inline tag."""
tag = self.questions.ask_existing_inline_tag(question="Which tag would you like to delete?")
@@ -307,6 +324,16 @@ class Application:
return
def load_vault(self, path_filter: str = None) -> None:
"""Load the vault.
Args:
path_filter (str, optional): Regex to filter notes by path.
"""
self.vault: Vault = Vault(config=self.config, dry_run=self.dry_run, path_filter=path_filter)
log.info(f"Indexed {self.vault.num_notes()} notes from {self.vault.vault_path}")
self.questions = Questions(vault=self.vault)
def rename_key(self) -> None:
"""Renames a key in the vault."""
@@ -406,30 +433,3 @@ class Application:
if note_to_review is None or note_to_review == "return":
break
changed_notes[note_to_review].print_diff()
def commit_changes(self) -> bool:
"""Write all changes to disk.
Returns:
True if changes were committed, False otherwise.
"""
changed_notes = self.vault.get_changed_notes()
if len(changed_notes) == 0:
print("\n")
alerts.notice("No changes to commit.\n")
return False
backup = questionary.confirm("Create backup before committing changes").ask()
if backup is None:
return False
if backup:
self.vault.backup()
if questionary.confirm(f"Commit {len(changed_notes)} changed files to disk?").ask():
self.vault.write()
alerts.success(f"{len(changed_notes)} changes committed to disk. Exiting")
return True
return False

View File

@@ -58,38 +58,6 @@ class VaultMetadata:
self.dict = dict(sorted(existing_metadata.items()))
def print_keys(self) -> None:
"""Print all metadata keys."""
columns = Columns(
sorted(self.dict.keys()),
equal=True,
expand=True,
title="All metadata keys in Obsidian vault",
)
print(columns)
def print_tags(self) -> None:
"""Print all tags."""
columns = Columns(
sorted(self.dict["tags"]),
equal=True,
expand=True,
title="All tags in Obsidian vault",
)
print(columns)
def print_metadata(self) -> None:
"""Print all metadata."""
table = Table(show_footer=False, show_lines=True)
table.add_column("Keys")
table.add_column("Values")
for key, value in sorted(self.dict.items()):
values: str | dict[str, list[str]] = (
"\n".join(sorted(value)) if isinstance(value, list) else value
)
table.add_row(f"[bold]{key}[/]", str(values))
Console().print(table)
def contains(self, key: str, value: str = None, is_regex: bool = False) -> bool:
"""Check if a key and/or a value exists in the metadata.
@@ -131,6 +99,38 @@ class VaultMetadata:
return False
def print_keys(self) -> None:
"""Print all metadata keys."""
columns = Columns(
sorted(self.dict.keys()),
equal=True,
expand=True,
title="All metadata keys in Obsidian vault",
)
print(columns)
def print_metadata(self) -> None:
"""Print all metadata."""
table = Table(show_footer=False, show_lines=True)
table.add_column("Keys")
table.add_column("Values")
for key, value in sorted(self.dict.items()):
values: str | dict[str, list[str]] = (
"\n".join(sorted(value)) if isinstance(value, list) else value
)
table.add_row(f"[bold]{key}[/]", str(values))
Console().print(table)
def print_tags(self) -> None:
"""Print all tags."""
columns = Columns(
sorted(self.dict["tags"]),
equal=True,
expand=True,
title="All tags in Obsidian vault",
)
print(columns)
def rename(self, key: str, value_1: str, value_2: str = None) -> bool:
"""Replace a value in the frontmatter.
@@ -244,29 +244,6 @@ class Frontmatter:
"""
return dict_contains(self.dict, key, value, is_regex)
def rename(self, key: str, value_1: str, value_2: str = None) -> bool:
"""Replace a value in the frontmatter.
Args:
key (str): Key to check.
value_1 (str): `With value_2` this is the value to rename. If `value_2` is None this is the renamed key
value_2 (str, Optional): New value.
Returns:
bool: True if a value was renamed
"""
if value_2 is None:
if key in self.dict and value_1 not in self.dict:
self.dict[value_1] = self.dict.pop(key)
return True
return False
if key in self.dict and value_1 in self.dict[key]:
self.dict[key] = sorted({value_2 if x == value_1 else x for x in self.dict[key]})
return True
return False
def delete(self, key: str, value_to_delete: str = None) -> bool:
"""Delete a value or key in the frontmatter. Regex is supported to allow deleting more than one key or value.
@@ -303,6 +280,29 @@ class Frontmatter:
"""
return self.dict != self.dict_original
def rename(self, key: str, value_1: str, value_2: str = None) -> bool:
"""Replace a value in the frontmatter.
Args:
key (str): Key to check.
value_1 (str): `With value_2` this is the value to rename. If `value_2` is None this is the renamed key
value_2 (str, Optional): New value.
Returns:
bool: True if a value was renamed
"""
if value_2 is None:
if key in self.dict and value_1 not in self.dict:
self.dict[value_1] = self.dict.pop(key)
return True
return False
if key in self.dict and value_1 in self.dict[key]:
self.dict[key] = sorted({value_2 if x == value_1 else x for x in self.dict[key]})
return True
return False
def to_yaml(self, sort_keys: bool = False) -> str:
"""Return the frontmatter as a YAML string.
@@ -348,19 +348,6 @@ class InlineMetadata:
"""
return f"InlineMetadata(inline_metadata={self.dict})"
def add(self, key: str, value: str | list[str] = None) -> bool:
"""Add a key and value to the frontmatter.
Args:
key (str): Key to add.
value (str, optional): Value to add.
Returns:
bool: True if the metadata was added
"""
# TODO: implement adding to inline metadata which requires knowing where in the note the metadata is to be added. In addition, unlike frontmatter, it is not possible to have multiple values for a key.
pass
def _grab_inline_metadata(self, file_content: str) -> dict[str, list[str]]:
"""Grab inline metadata from a note.
@@ -385,6 +372,19 @@ class InlineMetadata:
return clean_dictionary(inline_metadata)
def add(self, key: str, value: str | list[str] = None) -> bool:
"""Add a key and value to the frontmatter.
Args:
key (str): Key to add.
value (str, optional): Value to add.
Returns:
bool: True if the metadata was added
"""
# TODO: implement adding to inline metadata which requires knowing where in the note the metadata is to be added. In addition, unlike frontmatter, it is not possible to have multiple values for a key.
pass
def contains(self, key: str, value: str = None, is_regex: bool = False) -> bool:
"""Check if a key or value exists in the inline metadata.
@@ -398,29 +398,6 @@ class InlineMetadata:
"""
return dict_contains(self.dict, key, value, is_regex)
def rename(self, key: str, value_1: str, value_2: str = None) -> bool:
"""Replace a value in the inline metadata.
Args:
key (str): Key to check.
value_1 (str): `With value_2` this is the value to rename. If `value_2` is None this is the renamed key
value_2 (str, Optional): New value.
Returns:
bool: True if a value was renamed
"""
if value_2 is None:
if key in self.dict and value_1 not in self.dict:
self.dict[value_1] = self.dict.pop(key)
return True
return False
if key in self.dict and value_1 in self.dict[key]:
self.dict[key] = sorted({value_2 if x == value_1 else x for x in self.dict[key]})
return True
return False
def delete(self, key: str, value_to_delete: str = None) -> bool:
"""Delete a value or key in the inline metadata. Regex is supported to allow deleting more than one key or value.
@@ -457,6 +434,29 @@ class InlineMetadata:
"""
return self.dict != self.dict_original
def rename(self, key: str, value_1: str, value_2: str = None) -> bool:
"""Replace a value in the inline metadata.
Args:
key (str): Key to check.
value_1 (str): `With value_2` this is the value to rename. If `value_2` is None this is the renamed key
value_2 (str, Optional): New value.
Returns:
bool: True if a value was renamed
"""
if value_2 is None:
if key in self.dict and value_1 not in self.dict:
self.dict[value_1] = self.dict.pop(key)
return True
return False
if key in self.dict and value_1 in self.dict[key]:
self.dict[key] = sorted({value_2 if x == value_1 else x for x in self.dict[key]})
return True
return False
class InlineTags:
"""Representation of inline tags."""
@@ -512,21 +512,6 @@ class InlineTags:
return False
def rename(self, old_tag: str, new_tag: str) -> bool:
"""Replace an inline tag with another string.
Args:
old_tag (str): `With value_2` this is the value to rename. If `value_2` is None this is the renamed key
new_tag (str, Optional): New value.
Returns:
bool: True if a value was renamed
"""
if old_tag in self.list:
self.list = sorted([new_tag if i == old_tag else i for i in self.list])
return True
return False
def delete(self, tag_to_delete: str) -> bool:
"""Delete a specified inline tag. Regex is supported to allow deleting more than one tag.
@@ -550,3 +535,18 @@ class InlineTags:
bool: True if the metadata has changes.
"""
return self.list != self.list_original
def rename(self, old_tag: str, new_tag: str) -> bool:
"""Replace an inline tag with another string.
Args:
old_tag (str): `With value_2` this is the value to rename. If `value_2` is None this is the renamed key
new_tag (str, Optional): New value.
Returns:
bool: True if a value was renamed
"""
if old_tag in self.list:
self.list = sorted([new_tag if i == old_tag else i for i in self.list])
return True
return False

View File

@@ -64,6 +64,59 @@ class Note:
yield "inline_tags", self.inline_tags
yield "inline_metadata", self.inline_metadata
def _delete_inline_metadata(self, key: str, value: str = None) -> None:
"""Deletes an inline metadata key/value pair from the text of the note. This method does not remove the key/value from the metadata attribute of the note.
Args:
key (str): Key to delete.
value (str, optional): Value to delete.
"""
all_results = PATTERNS.find_inline_metadata.findall(self.file_content)
stripped_null_values = [tuple(filter(None, x)) for x in all_results]
for (_k, _v) in stripped_null_values:
if re.search(key, _k):
if value is None:
_k = re.escape(_k)
_v = re.escape(_v)
self.sub(rf"\[?{_k}:: ?{_v}]?", "", is_regex=True)
return
if re.search(value, _v):
_k = re.escape(_k)
_v = re.escape(_v)
self.sub(rf"({_k}::) ?{_v}", r"\1", is_regex=True)
def _rename_inline_metadata(self, key: str, value_1: str, value_2: str = None) -> None:
"""Replaces the inline metadata in the note with the current inline metadata object.
Args:
key (str): Key to rename.
value_1 (str): Value to replace OR new key name (if value_2 is None).
value_2 (str, optional): New value.
"""
all_results = PATTERNS.find_inline_metadata.findall(self.file_content)
stripped_null_values = [tuple(filter(None, x)) for x in all_results]
for (_k, _v) in stripped_null_values:
if re.search(key, _k):
if value_2 is None:
if re.search(rf"{key}[^\w\d_-]+", _k):
key_text = re.split(r"[^\w\d_-]+$", _k)[0]
key_markdown = re.split(r"^[\w\d_-]+", _k)[1]
self.sub(
rf"{key_text}{key_markdown}::",
rf"{value_1}{key_markdown}::",
)
else:
self.sub(f"{_k}::", f"{value_1}::")
else:
if re.search(key, _k) and re.search(value_1, _v):
_k = re.escape(_k)
_v = re.escape(_v)
self.sub(f"{_k}:: ?{_v}", f"{_k}:: {value_2}", is_regex=True)
def add_metadata(self, area: MetadataType, key: str, value: str | list[str] = None) -> bool:
"""Adds metadata to the note.
@@ -144,29 +197,6 @@ class Note:
return False
def _delete_inline_metadata(self, key: str, value: str = None) -> None:
"""Deletes an inline metadata key/value pair from the text of the note. This method does not remove the key/value from the metadata attribute of the note.
Args:
key (str): Key to delete.
value (str, optional): Value to delete.
"""
all_results = PATTERNS.find_inline_metadata.findall(self.file_content)
stripped_null_values = [tuple(filter(None, x)) for x in all_results]
for (_k, _v) in stripped_null_values:
if re.search(key, _k):
if value is None:
_k = re.escape(_k)
_v = re.escape(_v)
self.sub(rf"\[?{_k}:: ?{_v}]?", "", is_regex=True)
return
if re.search(value, _v):
_k = re.escape(_k)
_v = re.escape(_v)
self.sub(rf"({_k}::) ?{_v}", r"\1", is_regex=True)
def delete_inline_tag(self, tag: str) -> bool:
"""Deletes an inline tag from the `inline_tags` attribute AND removes the tag from the text of the note if it exists.
@@ -263,48 +293,27 @@ class Note:
Console().print(table)
def sub(self, pattern: str, replacement: str, is_regex: bool = False) -> None:
"""Substitutes text within the note.
def replace_frontmatter(self, sort_keys: bool = False) -> None:
"""Replaces the frontmatter in the note with the current frontmatter object."""
try:
current_frontmatter = PATTERNS.frontmatt_block_with_separators.search(
self.file_content
).group("frontmatter")
except AttributeError:
current_frontmatter = None
Args:
pattern (str): The pattern to replace (plain text or regular expression).
replacement (str): What to replace the pattern with.
is_regex (bool): Whether the pattern is a regex pattern or plain text.
"""
if not is_regex:
pattern = re.escape(pattern)
if current_frontmatter is None and self.frontmatter.dict == {}:
return
self.file_content = re.sub(pattern, replacement, self.file_content, re.MULTILINE)
new_frontmatter = self.frontmatter.to_yaml(sort_keys=sort_keys)
new_frontmatter = f"---\n{new_frontmatter}---\n"
def _rename_inline_metadata(self, key: str, value_1: str, value_2: str = None) -> None:
"""Replaces the inline metadata in the note with the current inline metadata object.
if current_frontmatter is None:
self.file_content = new_frontmatter + self.file_content
return
Args:
key (str): Key to rename.
value_1 (str): Value to replace OR new key name (if value_2 is None).
value_2 (str, optional): New value.
"""
all_results = PATTERNS.find_inline_metadata.findall(self.file_content)
stripped_null_values = [tuple(filter(None, x)) for x in all_results]
for (_k, _v) in stripped_null_values:
if re.search(key, _k):
if value_2 is None:
if re.search(rf"{key}[^\w\d_-]+", _k):
key_text = re.split(r"[^\w\d_-]+$", _k)[0]
key_markdown = re.split(r"^[\w\d_-]+", _k)[1]
self.sub(
rf"{key_text}{key_markdown}::",
rf"{value_1}{key_markdown}::",
)
else:
self.sub(f"{_k}::", f"{value_1}::")
else:
if re.search(key, _k) and re.search(value_1, _v):
_k = re.escape(_k)
_v = re.escape(_v)
self.sub(f"{_k}:: ?{_v}", f"{_k}:: {value_2}", is_regex=True)
current_frontmatter = re.escape(current_frontmatter)
self.sub(current_frontmatter, new_frontmatter, is_regex=True)
def rename_inline_tag(self, tag_1: str, tag_2: str) -> bool:
"""Renames an inline tag from the note ONLY if it's not in the metadata as well.
@@ -360,27 +369,18 @@ class Note:
return False
def replace_frontmatter(self, sort_keys: bool = False) -> None:
"""Replaces the frontmatter in the note with the current frontmatter object."""
try:
current_frontmatter = PATTERNS.frontmatt_block_with_separators.search(
self.file_content
).group("frontmatter")
except AttributeError:
current_frontmatter = None
def sub(self, pattern: str, replacement: str, is_regex: bool = False) -> None:
"""Substitutes text within the note.
if current_frontmatter is None and self.frontmatter.dict == {}:
return
Args:
pattern (str): The pattern to replace (plain text or regular expression).
replacement (str): What to replace the pattern with.
is_regex (bool): Whether the pattern is a regex pattern or plain text.
"""
if not is_regex:
pattern = re.escape(pattern)
new_frontmatter = self.frontmatter.to_yaml(sort_keys=sort_keys)
new_frontmatter = f"---\n{new_frontmatter}---\n"
if current_frontmatter is None:
self.file_content = new_frontmatter + self.file_content
return
current_frontmatter = re.escape(current_frontmatter)
self.sub(current_frontmatter, new_frontmatter, is_regex=True)
self.file_content = re.sub(pattern, replacement, self.file_content, re.MULTILINE)
def write(self, path: Path = None) -> None:
"""Writes the note's content to disk.

View File

@@ -17,13 +17,6 @@ class Patterns:
re.MULTILINE | re.X,
)
frontmatt_block_with_separators: Pattern[str] = re.compile(
r"^\s*(?P<frontmatter>---.*?---)", flags=re.DOTALL
)
frontmatt_block_no_separators: Pattern[str] = re.compile(
r"^\s*---(?P<frontmatter>.*?)---", flags=re.DOTALL
)
# This pattern will return a tuple of 4 values, two will be empty and will need to be stripped before processing further
find_inline_metadata: Pattern[str] = re.compile(
r""" # First look for in-text key values
(?:^\[| \[) # Find key with starting bracket
@@ -37,5 +30,13 @@ class Patterns:
re.X | re.MULTILINE,
)
validate_tag_text: Pattern[str] = re.compile(r"[ \|,;:\*\(\)\[\]\\\.\n#&]")
frontmatt_block_with_separators: Pattern[str] = re.compile(
r"^\s*(?P<frontmatter>---.*?---)", flags=re.DOTALL
)
frontmatt_block_no_separators: Pattern[str] = re.compile(
r"^\s*---(?P<frontmatter>.*?)---", flags=re.DOTALL
)
# This pattern will return a tuple of 4 values, two will be empty and will need to be stripped before processing further
validate_key_text: Pattern[str] = re.compile(r"[^-_\w\d\/\*\u263a-\U0001f645]")
validate_tag_text: Pattern[str] = re.compile(r"[ \|,;:\*\(\)\[\]\\\.\n#&]")

View File

@@ -64,9 +64,12 @@ class Questions:
"""
self.style = questionary.Style(
[
("separator", "bold fg:#6C6C6C"),
("instruction", "fg:#6C6C6C"),
("highlighted", "bold reverse"),
("qmark", "fg:#808080 bold"),
("question", "bold"),
("separator", "fg:#808080"),
("instruction", "fg:#808080"),
("highlighted", "fg:#c0c0c0 bold reverse"),
("text", ""),
("pointer", "bold"),
]
)
@@ -241,7 +244,10 @@ class Questions:
choices.append(questionary.Separator()) # type: ignore [arg-type]
choices.append({"name": "Cancel", "value": "cancel"})
return self.ask_selection(choices=choices, question="Select the type of metadata")
return self.ask_selection(
choices=choices,
question="Select the type of metadata",
)
def ask_confirm(self, question: str, default: bool = True) -> bool: # pragma: no cover
"""Ask the user to confirm an action.
@@ -253,13 +259,17 @@ class Questions:
Returns:
bool: True if the user confirms, otherwise False.
"""
return questionary.confirm(question, default=default, style=self.style).ask()
return questionary.confirm(
question, default=default, style=self.style, qmark="INPUT |"
).ask()
def ask_existing_inline_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,
style=self.style,
qmark="INPUT |",
).ask()
def ask_existing_key(self, question: str = "Enter a key") -> str: # pragma: no cover
@@ -272,8 +282,7 @@ class Questions:
str: A metadata key that exists in the vault.
"""
return questionary.text(
question,
validate=self._validate_key_exists,
question, validate=self._validate_key_exists, style=self.style, qmark="INPUT |"
).ask()
def ask_existing_keys_regex(self, question: str = "Regex for keys") -> str: # pragma: no cover
@@ -286,8 +295,7 @@ class Questions:
str: A regex for metadata keys that exist in the vault.
"""
return questionary.text(
question,
validate=self._validate_key_exists_regex,
question, validate=self._validate_key_exists_regex, style=self.style, qmark="INPUT |"
).ask()
def ask_existing_value(self, question: str = "Enter a value") -> str: # pragma: no cover
@@ -299,7 +307,9 @@ class Questions:
Returns:
str: A metadata value.
"""
return questionary.text(question, validate=self._validate_value).ask()
return questionary.text(
question, validate=self._validate_value, style=self.style, qmark="INPUT |"
).ask()
def ask_filter_path(self) -> str: # pragma: no cover
"""Ask the user for the path to the filter file.
@@ -311,6 +321,8 @@ class Questions:
"Regex to filter the notes being processed by their path:",
only_directories=False,
validate=self._validate_valid_vault_regex,
style=self.style,
qmark="INPUT |",
).ask()
if filter_path_regex is None:
raise typer.Exit(code=1)
@@ -331,6 +343,8 @@ class Questions:
return questionary.text(
question,
validate=self._validate_value_exists_regex,
style=self.style,
qmark="INPUT |",
).ask()
def ask_application_main(self) -> str: # pragma: no cover
@@ -345,7 +359,7 @@ class Questions:
return questionary.select(
"What do you want to do?",
choices=[
{"name": "Vault Actions, ", "value": "vault_actions"},
{"name": "Vault Actions", "value": "vault_actions"},
{"name": "Inspect Metadata", "value": "inspect_metadata"},
{"name": "Filter Notes in Scope", "value": "filter_notes"},
{"name": "Add Metadata", "value": "add_metadata"},
@@ -359,6 +373,7 @@ class Questions:
],
use_shortcuts=False,
style=self.style,
qmark="INPUT |",
).ask()
def ask_new_key(self, question: str = "New key name") -> str: # pragma: no cover
@@ -371,15 +386,13 @@ class Questions:
str: A new metadata key.
"""
return questionary.text(
question,
validate=self._validate_new_key,
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."""
return questionary.text(
question,
validate=self._validate_new_tag,
question, validate=self._validate_new_tag, style=self.style, qmark="INPUT |"
).ask()
def ask_new_value(self, question: str = "New value") -> str: # pragma: no cover
@@ -392,8 +405,7 @@ class Questions:
str: A new metadata value.
"""
return questionary.text(
question,
validate=self._validate_new_value,
question, validate=self._validate_new_value, style=self.style, qmark="INPUT |"
).ask()
def ask_selection(
@@ -413,4 +425,5 @@ class Questions:
choices=choices,
use_shortcuts=False,
style=self.style,
qmark="INPUT |",
).ask()

View File

@@ -5,6 +5,7 @@ import shutil
from pathlib import Path
import rich.repr
from rich import box
from rich.console import Console
from rich.progress import Progress, SpinnerColumn, TextColumn
from rich.prompt import Confirm
@@ -124,6 +125,7 @@ class Vault:
log.debug("Backing up vault")
if self.dry_run:
alerts.dryrun(f"Backup up vault to: {self.backup_path}")
print("\n")
return
try:
@@ -252,8 +254,10 @@ class Vault:
def list_editable_notes(self) -> None:
"""Print a list of notes within the scope that are being edited."""
for _note in self.notes:
print(_note.note_path.relative_to(self.vault_path))
table = Table(title="Notes in current scope", show_header=False, box=box.HORIZONTALS)
for _n, _note in enumerate(self.notes, start=1):
table.add_row(str(_n), str(_note.note_path.relative_to(self.vault_path)))
Console().print(table)
def num_excluded_notes(self) -> int:
"""Count number of excluded notes."""

View File

@@ -160,6 +160,19 @@ def test_info(test_vault, capsys):
assert captured.out == Regex(r"Backup +\│ None")
def test_list_editable_notes(test_vault, capsys) -> None:
"""Test listing editable notes."""
vault_path = test_vault
config = Config(config_path="tests/fixtures/test_vault_config.toml", vault_path=vault_path)
vault_config = config.vaults[0]
vault = Vault(config=vault_config)
vault.list_editable_notes()
captured = capsys.readouterr()
assert captured.out == Regex("Notes in current scope")
assert captured.out == Regex(r"1 +test1\.md")
def test_contains_inline_tag(test_vault) -> None:
"""Test if the vault contains an inline tag."""
vault_path = test_vault