feat(configuration): support multiple vaults in the configuration file (#6)

When multiple vaults are added to the configuration file, the script will prompt 
you to select one at runtime
This commit is contained in:
Nathaniel Landau
2023-01-24 10:32:56 -05:00
committed by GitHub
parent 5abab2ad20
commit 1e4fbcb4e2
23 changed files with 350 additions and 171 deletions

View File

@@ -5,7 +5,7 @@ default_stages: [commit, manual]
fail_fast: true
repos:
- repo: "https://github.com/commitizen-tools/commitizen"
rev: v2.39.1
rev: v2.40.0
hooks:
- id: commitizen
- id: commitizen-branch
@@ -39,6 +39,7 @@ repos:
- id: check-shebang-scripts-are-executable
- id: check-symlinks
- id: check-toml
exclude: broken_config_file\.toml
- id: check-vcs-permalinks
- id: check-xml
- id: check-yaml
@@ -60,7 +61,7 @@ repos:
entry: yamllint --strict --config-file .yamllint.yml
- repo: "https://github.com/charliermarsh/ruff-pre-commit"
rev: "v0.0.229"
rev: "v0.0.230"
hooks:
- id: ruff
args: ["--extend-ignore", "I001,D301,D401,PLR2004"]

View File

@@ -33,16 +33,27 @@ The script provides a menu of available actions. Make as many changes as you req
### Configuration
`obsidian-metadata` requires a configuration file at `~/.obsidian_metadata.toml`. On first run, this file will be created. Read the comments in this file to configure your preferences. This configuration file contains the following information.
`obsidian-metadata` requires a configuration file at `~/.obsidian_metadata.toml`. On first run, this file will be created. You can specify a new location for the configuration file with the `--config-file` option.
To add additional vaults, copy the default section and add the appropriate information. The script will prompt you to select a vault if multiple exist in the configuration file
Below is an example with two vaults.
```toml
["Vault One"] # Name of the vault.
# Path to your obsidian vault
vault = "/path/to/vault"
path = "/path/to/vault"
# Folders within the vault to ignore when indexing metadata
exclude_paths = [".git", ".obsidian"]
["Vault Two"]
path = "/path/to/second_vault"
exclude_paths = [".git", ".obsidian"]
```
To bypass the configuration file and specify a vault to use at runtime use the `--vault-path` option.
# Contributing

6
poetry.lock generated
View File

@@ -699,7 +699,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
name = "tomli"
version = "2.0.1"
description = "A lil' TOML parser"
category = "main"
category = "dev"
optional = false
python-versions = ">=3.7"
@@ -707,7 +707,7 @@ python-versions = ">=3.7"
name = "tomlkit"
version = "0.11.6"
description = "Style preserving TOML library"
category = "dev"
category = "main"
optional = false
python-versions = ">=3.6"
@@ -814,7 +814,7 @@ dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"]
[metadata]
lock-version = "1.1"
python-versions = "^3.10"
content-hash = "db4a41f0b94c3bf84fa548a6733da1cced1fedc2bdfdc6f80396dddbd5619bfd"
content-hash = "acbde0d9374261931e4f12f4ed8fcbc543008f68c4aed0a6748280a3999b3394"
[metadata.files]
absolufy-imports = [

View File

@@ -23,7 +23,7 @@
rich = "^13.2.0"
ruamel-yaml = "^0.17.21"
shellingham = "^1.4.0"
tomli = "^2.0.1"
tomlkit = "^0.11.6"
typer = "^0.7.0"
[tool.poetry.group.test.dependencies]

View File

@@ -1,5 +1,5 @@
"""Config module for obsidian frontmatter."""
from obsidian_metadata._config.config import Config
from obsidian_metadata._config.config import Config, VaultConfig
__all__ = ["Config"]
__all__ = ["Config", "VaultConfig"]

View File

@@ -1,61 +1,52 @@
"""Instantiate the configuration object."""
import re
import shutil
from pathlib import Path
from textwrap import dedent
from typing import Any
import questionary
import rich.repr
import tomlkit
import typer
from obsidian_metadata._utils import alerts, vault_validation
from obsidian_metadata._utils import Questions, alerts
from obsidian_metadata._utils.alerts import logger as log
try:
import tomllib
except ModuleNotFoundError:
import tomli as tomllib # type: ignore [no-redef]
DEFAULT_CONFIG_FILE: Path = Path(__file__).parent / "default.toml"
@rich.repr.auto
class Config:
"""Configuration class."""
"""Representation of a configuration file."""
def __init__(self, config_path: Path = None, vault_path: Path = None) -> None:
if vault_path is None:
self.config_path: Path = self._validate_config_path(Path(config_path))
self.config: dict[str, Any] = self._load_config()
self.config_content: str = self.config_path.read_text()
self.vault_path: Path = self._validate_vault_path(vault_path)
if self.config == {}:
log.error(f"Configuration file is empty: '{self.config_path}'")
raise typer.Exit(code=1)
else:
self.config_path = None
self.config = {
"command_line_vault": {"path": vault_path, "exclude_paths": [".git", ".obsidian"]}
}
try:
self.exclude_paths: list[Any] = self.config["exclude_paths"]
except KeyError:
self.exclude_paths = []
try:
self.metadata_location: str = self.config["metadata"]["metadata_location"]
except KeyError:
self.metadata_location = "frontmatter"
try:
self.tags_location: str = self.config["metadata"]["tags_location"]
except KeyError:
self.tags_location = "top"
self.vaults: list[VaultConfig] = [
VaultConfig(vault_name=key, vault_config=self.config[key]) for key in self.config
]
except TypeError as e:
log.error(f"Configuration file is invalid: '{self.config_path}'")
raise typer.Exit(code=1) from e
log.debug(f"Loaded configuration from '{self.config_path}'")
log.trace(self.config)
def __rich_repr__(self) -> rich.repr.Result: # pragma: no cover
"""Define rich representation of Vault."""
"""Define rich representation of the Config object."""
yield "config_path", self.config_path
yield "config_content",
yield "vault_path", self.vault_path
yield "metadata_location", self.metadata_location
yield "tags_location", self.tags_location
yield "exclude_paths", self.exclude_paths
yield "vaults", self.vaults
def _validate_config_path(self, config_path: Path | None) -> Path:
"""Load the configuration path."""
@@ -63,7 +54,7 @@ class Config:
config_path = Path(Path.home() / f".{__package__.split('.')[0]}.toml")
if not config_path.exists():
shutil.copy(DEFAULT_CONFIG_FILE, config_path)
self._write_default_config(config_path)
alerts.info(f"Created default configuration file at '{config_path}'")
return config_path.expanduser().resolve()
@@ -71,46 +62,67 @@ class Config:
def _load_config(self) -> dict[str, Any]:
"""Load the configuration file."""
try:
with self.config_path.open("rb") as f:
return tomllib.load(f)
except tomllib.TOMLDecodeError as e:
with open(self.config_path, encoding="utf-8") as fp:
return tomlkit.load(fp)
except tomlkit.exceptions.TOMLKitError as e:
alerts.error(f"Could not parse '{self.config_path}'")
raise typer.Exit(code=1) from e
def _write_default_config(self, path_to_config: Path) -> None:
"""Write the default configuration file when no config file is found."""
vault_path = Questions.ask_for_vault_path()
config_text = f"""\
# Add another vault by replicating this section and changing the name
["Vault 1"] # Name of the vault.
# Path to your obsidian vault
path = "{vault_path}"
# Folders within the vault to ignore when indexing metadata
exclude_paths = [".git", ".obsidian"]"""
path_to_config.write_text(dedent(config_text))
@rich.repr.auto
class VaultConfig:
"""Representation of a vault configuration."""
def __init__(self, vault_name: str, vault_config: dict) -> None:
"""Initialize the vault configuration."""
self.name: str = vault_name
self.config: dict = vault_config
try:
self.path = self._validate_vault_path(self.config["path"])
Path(self.config["path"]).expanduser().resolve()
except KeyError:
self.path = None
try:
self.exclude_paths = self.config["exclude_paths"]
except KeyError:
self.exclude_paths = []
def __rich_repr__(self) -> rich.repr.Result: # pragma: no cover
"""Define rich representation of a vault config."""
yield "name", self.name
yield "config", self.config
yield "path", self.path
yield "exclude_paths", self.exclude_paths
def _validate_vault_path(self, vault_path: Path | None) -> Path:
"""Validate the vault path."""
if vault_path is None:
try:
vault_path = Path(self.config["vault"]).expanduser().resolve()
except KeyError:
vault_path = Path("/I/Do/Not/Exist")
if not vault_path.exists(): # pragma: no cover
alerts.error(f"Vault path not found: '{vault_path}'")
vault_path = questionary.path(
"Enter a path to Obsidian vault:",
only_directories=True,
validate=vault_validation,
).ask()
if vault_path is None:
raise typer.Exit(code=1)
vault_path = Path(vault_path).expanduser().resolve()
self.write_config_value("vault", str(vault_path))
if not vault_path.exists():
alerts.error(f"Vault path not found: '{vault_path}'")
raise typer.Exit(code=1)
if not vault_path.is_dir():
alerts.error(f"Vault path is not a directory: '{vault_path}'")
raise typer.Exit(code=1)
return vault_path
def write_config_value(self, key: str, value: str | int) -> None:
"""Write a new value to the configuration file.
Args:
key (str): The key to write.
value (str|int): The value to write.
"""
self.config_content = re.sub(
rf"( *{key} = ['\"])[^'\"]*(['\"].*)", rf"\1{value}\2", self.config_content
)
alerts.notice(f"Writing new configuration for '{key}' to '{self.config_path}'")
self.config_path.write_text(self.config_content)

View File

@@ -1,5 +0,0 @@
# Path to your obsidian vault
vault = "/path/to/vault"
# Folders within the vault to ignore when indexing metadata
exclude_paths = [".git", ".obsidian"]

View File

@@ -2,6 +2,7 @@
from obsidian_metadata._utils import alerts
from obsidian_metadata._utils.alerts import LoggerManager
from obsidian_metadata._utils.questions import Questions
from obsidian_metadata._utils.utilities import (
clean_dictionary,
clear_screen,
@@ -9,7 +10,6 @@ from obsidian_metadata._utils.utilities import (
dict_values_to_lists_strings,
docstring_parameter,
remove_markdown_sections,
vault_validation,
version_callback,
)
@@ -21,6 +21,7 @@ __all__ = [
"dict_contains",
"docstring_parameter",
"LoggerManager",
"Questions",
"remove_markdown_sections",
"vault_validation",
"version_callback",

View File

@@ -0,0 +1,33 @@
"""Functions for asking questions to the user and validating responses."""
from pathlib import Path
import questionary
import typer
class Questions:
"""Class for asking questions to the user and validating responses."""
@staticmethod
def ask_for_vault_path() -> Path: # pragma: no cover
"""Ask the user for the path to their vault."""
vault_path = questionary.path(
"Enter a path to Obsidian vault:",
only_directories=True,
validate=Questions._validate_vault,
).ask()
if vault_path is None:
raise typer.Exit(code=1)
return Path(vault_path).expanduser().resolve()
@staticmethod
def _validate_vault(path: str) -> bool | str:
"""Validates the vault path."""
path_to_validate: Path = Path(path).expanduser().resolve()
if not path_to_validate.exists():
return f"Path does not exist: {path_to_validate}"
if not path_to_validate.is_dir():
return f"Path is not a directory: {path_to_validate}"
return True

View File

@@ -1,7 +1,6 @@
"""Utility functions."""
import re
from os import name, system
from pathlib import Path
from typing import Any
import typer
@@ -83,17 +82,6 @@ def version_callback(value: bool) -> None:
raise typer.Exit()
def vault_validation(path: str) -> bool | str:
"""Validates the vault path."""
path_to_validate: Path = Path(path).expanduser().resolve()
if not path_to_validate.exists():
return f"Path does not exist: {path_to_validate}"
if not path_to_validate.is_dir():
return f"Path is not a directory: {path_to_validate}"
return True
def docstring_parameter(*sub: Any) -> Any:
"""Decorator to replace variables within docstrings.

View File

@@ -4,11 +4,17 @@
from pathlib import Path
from typing import Optional
import questionary
import typer
from rich import print
from obsidian_metadata._config import Config
from obsidian_metadata._utils import alerts, docstring_parameter, version_callback
from obsidian_metadata._utils import (
alerts,
clear_screen,
docstring_parameter,
version_callback,
)
from obsidian_metadata.models import Application
app = typer.Typer(add_completion=False, no_args_is_help=True, rich_markup_mode="rich")
@@ -95,9 +101,6 @@ def main(
log_to_file,
)
config: Config = Config(config_path=config_file, vault_path=vault_path)
application = Application(dry_run=dry_run, config=config)
banner = r"""
___ _ _ _ _
/ _ \| |__ ___(_) __| (_) __ _ _ __
@@ -109,7 +112,28 @@ def main(
| | | | __/ || (_| | (_| | (_| | || (_| |
|_| |_|\___|\__\__,_|\__,_|\__,_|\__\__,_|
"""
clear_screen()
print(banner)
config: Config = Config(config_path=config_file, vault_path=vault_path)
if len(config.vaults) == 0:
typer.echo("No vaults configured. Exiting.")
raise typer.Exit(1)
if len(config.vaults) == 1:
application = Application(dry_run=dry_run, config=config.vaults[0])
else:
vault_names = [vault.name for vault in config.vaults]
vault_name = questionary.select(
"Select a vault to process:",
choices=vault_names,
).ask()
if vault_name is None:
raise typer.Exit(code=1)
vault_to_use = next(vault for vault in config.vaults if vault.name == vault_name)
application = Application(dry_run=dry_run, config=vault_to_use)
application.main_app()

View File

@@ -6,8 +6,8 @@ from typing import Any
import questionary
from rich import print
from obsidian_metadata._config import Config
from obsidian_metadata._utils import alerts, clear_screen
from obsidian_metadata._config import VaultConfig
from obsidian_metadata._utils import alerts
from obsidian_metadata._utils.alerts import logger as log
from obsidian_metadata.models import Patterns, Vault
@@ -22,7 +22,7 @@ class Application:
More info: https://questionary.readthedocs.io/en/stable/pages/advanced.html#create-questions-from-dictionaries
"""
def __init__(self, config: Config, dry_run: bool) -> None:
def __init__(self, config: VaultConfig, dry_run: bool) -> None:
self.config = config
self.dry_run = dry_run
self.custom_style = questionary.Style(
@@ -45,7 +45,6 @@ class Application:
def main_app(self) -> None: # noqa: C901
"""Questions for the main application."""
clear_screen()
self.load_vault()
while True:

View File

@@ -10,7 +10,7 @@ from rich.progress import Progress, SpinnerColumn, TextColumn
from rich.prompt import Confirm
from rich.table import Table
from obsidian_metadata._config import Config
from obsidian_metadata._config import VaultConfig
from obsidian_metadata._utils import alerts
from obsidian_metadata._utils.alerts import logger as log
from obsidian_metadata.models import Note, VaultMetadata
@@ -27,8 +27,8 @@ class Vault:
notes (list[Note]): List of all notes in the vault.
"""
def __init__(self, config: Config, dry_run: bool = False, path_filter: str = None):
self.vault_path: Path = config.vault_path
def __init__(self, config: VaultConfig, dry_run: bool = False, path_filter: str = None):
self.vault_path: Path = config.path
self.dry_run: bool = dry_run
self.backup_path: Path = self.vault_path.parent / f"{self.vault_path.name}.bak"
self.exclude_paths: list[Path] = []

View File

@@ -10,9 +10,10 @@ def test_load_vault(test_vault) -> None:
"""Test application."""
vault_path = test_vault
config = Config(config_path="tests/fixtures/test_vault_config.toml", vault_path=vault_path)
app = Application(config=config, dry_run=False)
vault_config = config.vaults[0]
app = Application(config=vault_config, dry_run=False)
app.load_vault()
assert app.dry_run is False
assert app.config == config
assert app.vault.num_notes() == 2
assert app.config == vault_config
assert app.vault.num_notes() == 3

View File

@@ -1,28 +1,109 @@
# type: ignore
"""Tests for the configuration module."""
import re
from pathlib import Path
from textwrap import dedent
import pytest
import typer
from obsidian_metadata._config import Config
def test_first_run(tmp_path):
"""Test creating a config on first run."""
config_file = Path(tmp_path / "config.toml")
vault_path = Path(tmp_path / "vault/")
vault_path.mkdir()
def test_broken_config_file(capsys) -> None:
"""Test loading a broken config file."""
config_file = Path("tests/fixtures/broken_config_file.toml")
config = Config(config_path=config_file, vault_path=vault_path)
with pytest.raises(typer.Exit):
Config(config_path=config_file)
captured = capsys.readouterr()
assert "Could not parse" in captured.out
def test_vault_path_errors(tmp_path, capsys) -> None:
"""Test loading a config file with a vault path that doesn't exist."""
config_file = Path(tmp_path / "config.toml")
with pytest.raises(typer.Exit):
Config(config_path=config_file, vault_path=Path("tests/fixtures/does_not_exist"))
captured = capsys.readouterr()
assert "Vault path not found" in captured.out
with pytest.raises(typer.Exit):
Config(config_path=config_file, vault_path=Path("tests/fixtures/sample_note.md"))
captured = capsys.readouterr()
assert "Vault path is not a directory" in captured.out
def test_multiple_vaults_okay() -> None:
"""Test multiple vaults."""
config_file = Path("tests/fixtures/multiple_vaults.toml")
config = Config(config_path=config_file)
assert config.config == {
"Sample Vault": {
"exclude_paths": [".git", ".obsidian", "ignore_folder"],
"path": "tests/fixtures/sample_vault",
},
"Test Vault": {
"exclude_paths": [".git", ".obsidian", "ignore_folder"],
"path": "tests/fixtures/test_vault",
},
}
assert len(config.vaults) == 2
assert config.vaults[0].name == "Sample Vault"
assert config.vaults[0].path == Path("tests/fixtures/sample_vault").expanduser().resolve()
assert config.vaults[0].exclude_paths == [".git", ".obsidian", "ignore_folder"]
assert config.vaults[1].name == "Test Vault"
assert config.vaults[1].path == Path("tests/fixtures/test_vault").expanduser().resolve()
assert config.vaults[1].exclude_paths == [".git", ".obsidian", "ignore_folder"]
def test_single_vault() -> None:
"""Test multiple vaults."""
config_file = Path("tests/fixtures/test_vault_config.toml")
config = Config(config_path=config_file)
assert config.config == {
"Test Vault": {
"exclude_paths": [".git", ".obsidian", "ignore_folder"],
"path": "tests/fixtures/test_vault",
}
}
assert len(config.vaults) == 1
assert config.vaults[0].name == "Test Vault"
assert config.vaults[0].path == Path("tests/fixtures/test_vault").expanduser().resolve()
assert config.vaults[0].exclude_paths == [".git", ".obsidian", "ignore_folder"]
def test_no_config_no_vault(tmp_path, mocker) -> None:
"""Test creating a config on first run."""
fake_vault = Path(tmp_path / "vault")
fake_vault.mkdir()
mocker.patch(
"obsidian_metadata._config.config.Questions.ask_for_vault_path", return_value=fake_vault
)
config_file = Path(tmp_path / "config.toml")
Config(config_path=config_file)
content = config_file.read_text()
sample_config = f"""\
# Add another vault by replicating this section and changing the name
["Vault 1"] # Name of the vault.
# Path to your obsidian vault
path = "{str(fake_vault)}"
# Folders within the vault to ignore when indexing metadata
exclude_paths = [".git", ".obsidian"]"""
assert config_file.exists() is True
config.write_config_value("vault", str(vault_path))
content = config_file.read_text()
assert config.vault_path == vault_path
assert re.search(str(vault_path), content) is not None
assert content == dedent(sample_config)
def test_parse_config():
"""Test parsing a config file."""
config = Config(config_path="tests/fixtures/test_vault_config.toml", vault_path=None)
assert config.vault_path == Path(Path.cwd() / "tests/fixtures/test_vault")
new_config = Config(config_path=config_file)
assert new_config.config == {
"Vault 1": {
"path": str(fake_vault),
"exclude_paths": [".git", ".obsidian"],
}
}

View File

@@ -0,0 +1,6 @@
["Sample Vault]
exclude_paths = [".git", ".obsidian", "ignore_folder"]
path = "tests/fixtures/sample_vault"
["Test Vault"]
exclude_paths = [".git", ".obsidian", "ignore_folder"]
path = "tests/fixtures/test_vault"

6
tests/fixtures/multiple_vaults.toml vendored Normal file
View File

@@ -0,0 +1,6 @@
["Sample Vault"]
exclude_paths = [".git", ".obsidian", "ignore_folder"]
path = "tests/fixtures/sample_vault"
["Test Vault"]
exclude_paths = [".git", ".obsidian", "ignore_folder"]
path = "tests/fixtures/test_vault"

View File

@@ -1,8 +1,3 @@
vault = "tests/fixtures/sample_vault"
# folders to ignore when parsing content
["Sample Vault"]
exclude_paths = [".git", ".obsidian", "ignore_folder"]
[metadata]
metadata_location = "frontmatter" # "frontmatter", "top", "bottom"
tags_location = "top" # "frontmatter", "top", "bottom"
path = "tests/fixtures/sample_vault"

View File

@@ -8,6 +8,7 @@ tags:
- ignored_file_tag1
author: author name
type: ["article", "note"]
ignored_frontmatter: ignore_me
---
#inline_tag_top1 #inline_tag_top2
#ignored_file_tag2

View File

@@ -1,8 +1,3 @@
vault = "tests/fixtures/test_vault"
# folders to ignore when parsing content
["Test Vault"]
exclude_paths = [".git", ".obsidian", "ignore_folder"]
[metadata]
metadata_location = "frontmatter" # "frontmatter", "top", "bottom"
tags_location = "top" # "frontmatter", "top", "bottom"
path = "tests/fixtures/test_vault"

12
tests/questions_test.py Normal file
View File

@@ -0,0 +1,12 @@
# type: ignore
"""Test the questions class."""
from obsidian_metadata._utils import Questions
def test_vault_validation():
"""Test vault validation."""
assert Questions._validate_vault("tests/") is True
assert "Path is not a directory" in Questions._validate_vault("pyproject.toml")
assert "Path does not exist" in Questions._validate_vault("tests/vault2")

View File

@@ -7,7 +7,6 @@ from obsidian_metadata._utils import (
dict_contains,
dict_values_to_lists_strings,
remove_markdown_sections,
vault_validation,
)
@@ -67,13 +66,6 @@ def test_dict_values_to_lists_strings():
}
def test_vault_validation():
"""Test vault validation."""
assert vault_validation("tests/") is True
assert "Path is not a directory" in vault_validation("pyproject.toml")
assert "Path does not exist" in vault_validation("tests/vault2")
def test_remove_markdown_sections():
"""Test removing markdown sections."""
text: str = """

View File

@@ -12,16 +12,18 @@ def test_vault_creation(test_vault):
"""Test creating a Vault object."""
vault_path = test_vault
config = Config(config_path="tests/fixtures/test_vault_config.toml", vault_path=vault_path)
vault = Vault(config=config)
vault_config = config.vaults[0]
vault = Vault(config=vault_config)
assert vault.vault_path == vault_path
assert vault.backup_path == Path(f"{vault_path}.bak")
assert vault.dry_run is False
assert str(vault.exclude_paths[0]) == Regex(r".*\.git")
assert vault.num_notes() == 2
assert vault.num_notes() == 3
assert vault.metadata.dict == {
"Inline Tags": [
"ignored_file_tag2",
"inline_tag_bottom1",
"inline_tag_bottom2",
"inline_tag_top1",
@@ -30,24 +32,29 @@ def test_vault_creation(test_vault):
"intext_tag2",
"shared_tag",
],
"author": ["author name"],
"bottom_key1": ["bottom_key1_value"],
"bottom_key2": ["bottom_key2_value"],
"date_created": ["2022-12-22"],
"emoji_📅_key": ["emoji_📅_key_value"],
"frontmatter_Key1": ["author name"],
"frontmatter_Key2": ["article", "note"],
"ignored_frontmatter": ["ignore_me"],
"intext_key": ["intext_value"],
"shared_key1": ["shared_key1_value"],
"shared_key2": ["shared_key2_value1", "shared_key2_value2"],
"tags": [
"frontmatter_tag1",
"frontmatter_tag2",
"frontmatter_tag3",
"ignored_file_tag1",
"shared_tag",
"📅/frontmatter_tag3",
],
"top_key1": ["top_key1_value"],
"top_key2": ["top_key2_value"],
"top_key3": ["top_key3_value_as_link"],
"type": ["article", "note"],
}
@@ -55,13 +62,15 @@ def test_get_filtered_notes(sample_vault) -> None:
"""Test filtering notes."""
vault_path = sample_vault
config = Config(config_path="tests/fixtures/sample_vault_config.toml", vault_path=vault_path)
vault = Vault(config=config, path_filter="front")
vault_config = config.vaults[0]
vault = Vault(config=vault_config, path_filter="front")
assert vault.num_notes() == 4
vault_path = sample_vault
config = Config(config_path="tests/fixtures/sample_vault_config.toml", vault_path=vault_path)
vault2 = Vault(config=config, path_filter="mixed")
vault_config = config.vaults[0]
vault2 = Vault(config=vault_config, path_filter="mixed")
assert vault2.num_notes() == 1
@@ -70,7 +79,8 @@ def test_backup(test_vault, capsys):
"""Test backing up the vault."""
vault_path = test_vault
config = Config(config_path="tests/fixtures/test_vault_config.toml", vault_path=vault_path)
vault = Vault(config=config, dry_run=False)
vault_config = config.vaults[0]
vault = Vault(config=vault_config)
vault.backup()
@@ -88,7 +98,8 @@ def test_backup_dryrun(test_vault, capsys):
"""Test backing up the vault."""
vault_path = test_vault
config = Config(config_path="tests/fixtures/test_vault_config.toml", vault_path=vault_path)
vault = Vault(config=config, dry_run=True)
vault_config = config.vaults[0]
vault = Vault(config=vault_config, dry_run=True)
print(f"vault.dry_run: {vault.dry_run}")
vault.backup()
@@ -102,7 +113,8 @@ def test_delete_backup(test_vault, capsys):
"""Test deleting the vault backup."""
vault_path = test_vault
config = Config(config_path="tests/fixtures/test_vault_config.toml", vault_path=vault_path)
vault = Vault(config=config, dry_run=False)
vault_config = config.vaults[0]
vault = Vault(config=vault_config)
vault.backup()
vault.delete_backup()
@@ -121,7 +133,8 @@ def test_delete_backup_dryrun(test_vault, capsys):
"""Test deleting the vault backup."""
vault_path = test_vault
config = Config(config_path="tests/fixtures/test_vault_config.toml", vault_path=vault_path)
vault = Vault(config=config, dry_run=True)
vault_config = config.vaults[0]
vault = Vault(config=vault_config, dry_run=True)
Path.mkdir(vault.backup_path)
vault.delete_backup()
@@ -135,7 +148,8 @@ def test_info(test_vault, capsys):
"""Test printing vault information."""
vault_path = test_vault
config = Config(config_path="tests/fixtures/test_vault_config.toml", vault_path=vault_path)
vault = Vault(config=config)
vault_config = config.vaults[0]
vault = Vault(config=vault_config)
vault.info()
@@ -149,7 +163,8 @@ def test_contains_inline_tag(test_vault) -> None:
"""Test if the vault contains an inline tag."""
vault_path = test_vault
config = Config(config_path="tests/fixtures/test_vault_config.toml", vault_path=vault_path)
vault = Vault(config=config)
vault_config = config.vaults[0]
vault = Vault(config=vault_config)
assert vault.contains_inline_tag("tag") is False
assert vault.contains_inline_tag("intext_tag2") is True
@@ -159,7 +174,8 @@ def test_contains_metadata(test_vault) -> None:
"""Test if the vault contains a metadata key."""
vault_path = test_vault
config = Config(config_path="tests/fixtures/test_vault_config.toml", vault_path=vault_path)
vault = Vault(config=config)
vault_config = config.vaults[0]
vault = Vault(config=vault_config)
assert vault.contains_metadata("key") is False
assert vault.contains_metadata("top_key1") is True
@@ -171,11 +187,13 @@ def test_delete_inline_tag(test_vault) -> None:
"""Test deleting an inline tag."""
vault_path = test_vault
config = Config(config_path="tests/fixtures/test_vault_config.toml", vault_path=vault_path)
vault = Vault(config=config)
vault_config = config.vaults[0]
vault = Vault(config=vault_config)
assert vault.delete_inline_tag("no tag") is False
assert vault.delete_inline_tag("intext_tag2") is True
assert vault.metadata.dict["Inline Tags"] == [
"ignored_file_tag2",
"inline_tag_bottom1",
"inline_tag_bottom2",
"inline_tag_top1",
@@ -189,15 +207,16 @@ def test_delete_metadata(test_vault) -> None:
"""Test deleting a metadata key/value."""
vault_path = test_vault
config = Config(config_path="tests/fixtures/test_vault_config.toml", vault_path=vault_path)
vault = Vault(config=config)
vault_config = config.vaults[0]
vault = Vault(config=vault_config)
assert vault.delete_metadata("no key") == 0
assert vault.delete_metadata("top_key1", "no_value") == 0
assert vault.delete_metadata("top_key1", "top_key1_value") == 1
assert vault.delete_metadata("top_key1", "top_key1_value") == 2
assert vault.metadata.dict["top_key1"] == []
assert vault.delete_metadata("top_key2") == 1
assert vault.delete_metadata("top_key2") == 2
assert "top_key2" not in vault.metadata.dict
@@ -205,11 +224,13 @@ def test_rename_inline_tag(test_vault) -> None:
"""Test renaming an inline tag."""
vault_path = test_vault
config = Config(config_path="tests/fixtures/test_vault_config.toml", vault_path=vault_path)
vault = Vault(config=config)
vault_config = config.vaults[0]
vault = Vault(config=vault_config)
assert vault.rename_inline_tag("no tag", "new_tag") is False
assert vault.rename_inline_tag("intext_tag2", "new_tag") is True
assert vault.metadata.dict["Inline Tags"] == [
"ignored_file_tag2",
"inline_tag_bottom1",
"inline_tag_bottom2",
"inline_tag_top1",
@@ -224,7 +245,8 @@ def test_rename_metadata(test_vault) -> None:
"""Test renaming a metadata key/value."""
vault_path = test_vault
config = Config(config_path="tests/fixtures/test_vault_config.toml", vault_path=vault_path)
vault = Vault(config=config)
vault_config = config.vaults[0]
vault = Vault(config=vault_config)
assert vault.rename_metadata("no key", "new_key") is False
assert vault.rename_metadata("tags", "nonexistent_value", "new_vaule") is False
@@ -232,6 +254,8 @@ def test_rename_metadata(test_vault) -> None:
assert vault.rename_metadata("tags", "frontmatter_tag1", "new_vaule") is True
assert vault.metadata.dict["tags"] == [
"frontmatter_tag2",
"frontmatter_tag3",
"ignored_file_tag1",
"new_vaule",
"shared_tag",
"📅/frontmatter_tag3",
@@ -241,6 +265,8 @@ def test_rename_metadata(test_vault) -> None:
assert "tags" not in vault.metadata.dict
assert vault.metadata.dict["new_key"] == [
"frontmatter_tag2",
"frontmatter_tag3",
"ignored_file_tag1",
"new_vaule",
"shared_tag",
"📅/frontmatter_tag3",