feat: export metadata (#14)

* docs(readme): fix line breaks

* feat: export metadata to a CSV

* fix: finalize colors for questions

* feat: inspect frontmatter, inline, and tags separately

* feat: export metadata to JSON

* fix: do not count in-page links as tags

* ci(codecov): adjust patch target percentage down

* feat(metadata): export CSV or JSON from command line
This commit is contained in:
Nathaniel Landau
2023-02-02 17:09:31 -05:00
committed by GitHub
parent 4a29945de2
commit 8e040aeba4
20 changed files with 1668 additions and 1197 deletions

View File

@@ -18,6 +18,15 @@ from obsidian_metadata.models.vault import Vault
PATTERNS = Patterns()
# Reset the default style of the questionary prompts qmark
questionary.prompts.checkbox.DEFAULT_STYLE = questionary.Style([("qmark", "")])
questionary.prompts.common.DEFAULT_STYLE = questionary.Style([("qmark", "")])
questionary.prompts.confirm.DEFAULT_STYLE = questionary.Style([("qmark", "")])
questionary.prompts.confirm.DEFAULT_STYLE = questionary.Style([("qmark", "")])
questionary.prompts.path.DEFAULT_STYLE = questionary.Style([("qmark", "")])
questionary.prompts.select.DEFAULT_STYLE = questionary.Style([("qmark", "")])
questionary.prompts.text.DEFAULT_STYLE = questionary.Style([("qmark", "")])
class Questions:
"""Class for asking questions to the user and validating responses with questionary."""
@@ -64,13 +73,13 @@ class Questions:
"""
self.style = questionary.Style(
[
("qmark", "fg:#729fcf bold"),
("question", "fg:#729fcf bold"),
("qmark", "bold"),
("question", "bold"),
("separator", "fg:#808080"),
("instruction", "fg:#808080"),
("highlighted", "fg:#729fcf bold underline"),
("highlighted", "bold underline"),
("text", ""),
("pointer", "fg:#729fcf bold"),
("pointer", "bold"),
]
)
self.vault = vault
@@ -85,7 +94,7 @@ class Questions:
if len(text) < 1:
return "Tag cannot be empty"
if not self.vault.contains_inline_tag(text):
if not self.vault.metadata.contains(area=MetadataType.TAGS, value=text):
return f"'{text}' does not exist as a tag in the vault"
return True
@@ -99,7 +108,7 @@ class Questions:
if len(text) < 1:
return "Key cannot be empty"
if not self.vault.metadata.contains(text):
if not self.vault.metadata.contains(area=MetadataType.KEYS, key=text):
return f"'{text}' does not exist as a key in the vault"
return True
@@ -118,7 +127,7 @@ class Questions:
except re.error as error:
return f"Invalid regex: {error}"
if not self.vault.metadata.contains(text, is_regex=True):
if not self.vault.metadata.contains(area=MetadataType.KEYS, key=text, is_regex=True):
return f"'{text}' does not exist as a key in the vault"
return True
@@ -169,7 +178,9 @@ class Questions:
if len(text) < 1:
return "Value cannot be empty"
if self.key is not None and self.vault.metadata.contains(self.key, text):
if self.key is not None and self.vault.metadata.contains(
area=MetadataType.ALL, key=self.key, value=text
):
return f"{self.key}:{text} already exists"
return True
@@ -219,7 +230,9 @@ class Questions:
if len(text) == 0:
return True
if self.key is not None and not self.vault.metadata.contains(self.key, text):
if self.key is not None and not self.vault.metadata.contains(
area=MetadataType.ALL, key=self.key, value=text
):
return f"{self.key}:{text} does not exist"
return True
@@ -241,11 +254,42 @@ class Questions:
except re.error as error:
return f"Invalid regex: {error}"
if self.key is not None and not self.vault.metadata.contains(self.key, text, is_regex=True):
if self.key is not None and not self.vault.metadata.contains(
area=MetadataType.ALL, key=self.key, value=text, is_regex=True
):
return f"No values in {self.key} match regex: {text}"
return True
def ask_application_main(self) -> str: # pragma: no cover
"""Selectable list for the main application interface.
Args:
style (questionary.Style): The style to use for the question.
Returns:
str: The selected application.
"""
return questionary.select(
"What do you want to do?",
choices=[
{"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"},
{"name": "Rename Metadata", "value": "rename_metadata"},
{"name": "Delete Metadata", "value": "delete_metadata"},
questionary.Separator("-------------------------------"),
{"name": "Review Changes", "value": "review_changes"},
{"name": "Commit Changes", "value": "commit_changes"},
questionary.Separator("-------------------------------"),
{"name": "Quit", "value": "abort"},
],
use_shortcuts=False,
style=self.style,
qmark="INPUT |",
).ask()
def ask_area(self) -> MetadataType | str: # pragma: no cover
"""Ask the user for the metadata area to work on.
@@ -361,35 +405,6 @@ class Questions:
qmark="INPUT |",
).ask()
def ask_application_main(self) -> str: # pragma: no cover
"""Selectable list for the main application interface.
Args:
style (questionary.Style): The style to use for the question.
Returns:
str: The selected application.
"""
return questionary.select(
"What do you want to do?",
choices=[
{"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"},
{"name": "Rename Metadata", "value": "rename_metadata"},
{"name": "Delete Metadata", "value": "delete_metadata"},
questionary.Separator("-------------------------------"),
{"name": "Review Changes", "value": "review_changes"},
{"name": "Commit Changes", "value": "commit_changes"},
questionary.Separator("-------------------------------"),
{"name": "Quit", "value": "abort"},
],
use_shortcuts=False,
style=self.style,
qmark="INPUT |",
).ask()
def ask_new_key(self, question: str = "New key name") -> str: # pragma: no cover
"""Ask the user for a new metadata key.
@@ -422,7 +437,7 @@ class Questions:
question, validate=self._validate_new_value, style=self.style, qmark="INPUT |"
).ask()
def ask_number(self, question: str = "Enter a number") -> int:
def ask_number(self, question: str = "Enter a number") -> int: # pragma: no cover
"""Ask the user for a number.
Args:
@@ -435,6 +450,17 @@ class Questions:
question, validate=self._validate_number, style=self.style, qmark="INPUT |"
).ask()
def ask_path(self, question: str = "Enter a path") -> str: # pragma: no cover
"""Ask the user for a path.
Args:
question (str, optional): The question to ask. Defaults to "Enter a path".
Returns:
str: A path.
"""
return questionary.path(question, style=self.style, qmark="INPUT |").ask()
def ask_selection(
self, choices: list[Any], question: str = "Select an option"
) -> Any: # pragma: no cover