feat(app): limit scope of notes with one or more filters (#13)

* style: rename `VaultMetadata.add_metadata` to `VaultMetadata.index_metadata`

* refactor(vault): refactor filtering notes

* fix(application): improve usage display

* fix(application): improve colors of questions

* feat(application): limit the scope of notes to be processed with one or more filters

* build(deps): update identify
This commit is contained in:
Nathaniel Landau
2023-02-01 15:00:57 -05:00
committed by GitHub
parent 6909738218
commit 4a29945de2
15 changed files with 418 additions and 171 deletions

View File

@@ -44,6 +44,36 @@ def test_notice(capsys):
assert captured.out == "NOTICE | This prints in notice\n"
def test_alerts_debug(capsys):
"""Test debug."""
alerts.debug("This prints in debug")
captured = capsys.readouterr()
assert captured.out == "DEBUG | This prints in debug\n"
def test_usage(capsys):
"""Test usage."""
alerts.usage("This prints in usage")
captured = capsys.readouterr()
assert captured.out == "USAGE | This prints in usage\n"
alerts.usage(
"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua"
)
captured = capsys.readouterr()
assert "USAGE | Lorem ipsum dolor sit amet" in captured.out
assert " | incididunt ut labore et dolore magna aliqua" in captured.out
alerts.usage(
"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua",
width=20,
)
captured = capsys.readouterr()
assert "USAGE | Lorem ipsum dolor" in captured.out
assert " | sit amet," in captured.out
assert " | adipisicing elit," in captured.out
def test_info(capsys):
"""Test info."""
alerts.info("This prints in info")

View File

@@ -19,19 +19,19 @@ from tests.helpers import Regex
def test_instantiate_application(test_application) -> None:
"""Test application."""
app = test_application
app.load_vault()
app._load_vault()
assert app.dry_run is False
assert app.config.name == "command_line_vault"
assert app.config.exclude_paths == [".git", ".obsidian"]
assert app.dry_run is False
assert app.vault.num_notes() == 13
assert len(app.vault.all_notes) == 13
def test_abort(test_application, mocker, capsys) -> None:
"""Test renaming a key."""
app = test_application
app.load_vault()
app._load_vault()
mocker.patch(
"obsidian_metadata.models.application.Questions.ask_application_main",
return_value="abort",
@@ -45,7 +45,7 @@ def test_abort(test_application, mocker, capsys) -> None:
def test_add_metadata_frontmatter_success(test_application, mocker, capsys) -> None:
"""Test adding new metadata to the vault."""
app = test_application
app.load_vault()
app._load_vault()
mocker.patch(
"obsidian_metadata.models.application.Questions.ask_application_main",
side_effect=["add_metadata", KeyError],
@@ -72,7 +72,7 @@ def test_add_metadata_frontmatter_success(test_application, mocker, capsys) -> N
def test_delete_inline_tag(test_application, mocker, capsys) -> None:
"""Test renaming an inline tag."""
app = test_application
app.load_vault()
app._load_vault()
mocker.patch(
"obsidian_metadata.models.application.Questions.ask_application_main",
side_effect=["delete_metadata", KeyError],
@@ -113,7 +113,7 @@ def test_delete_inline_tag(test_application, mocker, capsys) -> None:
def test_delete_key(test_application, mocker, capsys) -> None:
"""Test renaming an inline tag."""
app = test_application
app.load_vault()
app._load_vault()
mocker.patch(
"obsidian_metadata.models.application.Questions.ask_application_main",
side_effect=["delete_metadata", KeyError],
@@ -156,7 +156,7 @@ def test_delete_key(test_application, mocker, capsys) -> None:
def test_delete_value(test_application, mocker, capsys) -> None:
"""Test renaming an inline tag."""
app = test_application
app.load_vault()
app._load_vault()
mocker.patch(
"obsidian_metadata.models.application.Questions.ask_application_main",
side_effect=["delete_metadata", KeyError],
@@ -202,17 +202,17 @@ def test_delete_value(test_application, mocker, capsys) -> None:
)
def test_filter_notes_filter(test_application, mocker, capsys) -> None:
def test_filter_notes(test_application, mocker, capsys) -> None:
"""Test renaming a key."""
app = test_application
app.load_vault()
app._load_vault()
mocker.patch(
"obsidian_metadata.models.application.Questions.ask_application_main",
side_effect=["filter_notes", KeyError],
)
mocker.patch(
"obsidian_metadata.models.application.Questions.ask_selection",
side_effect=["apply_filter", "list_notes", "back"],
side_effect=["apply_path_filter", "list_notes", "back"],
)
mocker.patch(
"obsidian_metadata.models.application.Questions.ask_filter_path",
@@ -232,25 +232,61 @@ def test_filter_notes_filter(test_application, mocker, capsys) -> None:
)
mocker.patch(
"obsidian_metadata.models.application.Questions.ask_selection",
side_effect=["apply_filter", "list_notes", "back"],
side_effect=["apply_metadata_filter", "list_notes", "back"],
)
mocker.patch(
"obsidian_metadata.models.application.Questions.ask_filter_path",
"obsidian_metadata.models.application.Questions.ask_existing_key",
return_value="on_one_note",
)
mocker.patch(
"obsidian_metadata.models.application.Questions.ask_existing_value",
return_value="",
)
with pytest.raises(KeyError):
app.application_main()
captured = capsys.readouterr()
assert captured.out == Regex(r"SUCCESS +\| Loaded all.*\d+.*total notes", re.DOTALL)
assert captured.out == Regex(r"SUCCESS +\| Loaded.*1.*notes from.*\d+.*total", re.DOTALL)
assert "02 inline/inline 2.md" in captured.out
assert "03 mixed/mixed 1.md" not in captured.out
def test_filter_clear(test_application, mocker, capsys) -> None:
"""Test clearing filters."""
app = test_application
app._load_vault()
mocker.patch(
"obsidian_metadata.models.application.Questions.ask_application_main",
side_effect=["filter_notes", "filter_notes", KeyError],
)
mocker.patch(
"obsidian_metadata.models.application.Questions.ask_selection",
side_effect=["apply_metadata_filter", "list_filters", "list_notes", "back"],
)
mocker.patch(
"obsidian_metadata.models.application.Questions.ask_existing_key",
return_value="on_one_note",
)
mocker.patch(
"obsidian_metadata.models.application.Questions.ask_existing_value",
return_value="",
)
mocker.patch(
"obsidian_metadata.models.application.Questions.ask_number",
return_value="1",
)
with pytest.raises(KeyError):
app.application_main()
captured = capsys.readouterr()
assert "02 inline/inline 2.md" in captured.out
assert "03 mixed/mixed 1.md" in captured.out
assert "01 frontmatter/frontmatter 4.md" in captured.out
assert "04 no metadata/no_metadata_1.md " in captured.out
def test_inspect_metadata_all(test_application, mocker, capsys) -> None:
"""Test backing up a vault."""
app = test_application
app.load_vault()
app._load_vault()
mocker.patch(
"obsidian_metadata.models.application.Questions.ask_application_main",
side_effect=["inspect_metadata", KeyError],
@@ -269,7 +305,7 @@ def test_inspect_metadata_all(test_application, mocker, capsys) -> None:
def test_rename_inline_tag(test_application, mocker, capsys) -> None:
"""Test renaming an inline tag."""
app = test_application
app.load_vault()
app._load_vault()
mocker.patch(
"obsidian_metadata.models.application.Questions.ask_application_main",
side_effect=["rename_metadata", KeyError],
@@ -318,7 +354,7 @@ def test_rename_inline_tag(test_application, mocker, capsys) -> None:
def test_rename_key(test_application, mocker, capsys) -> None:
"""Test renaming a key."""
app = test_application
app.load_vault()
app._load_vault()
mocker.patch(
"obsidian_metadata.models.application.Questions.ask_application_main",
side_effect=["rename_metadata", KeyError],
@@ -367,7 +403,7 @@ def test_rename_key(test_application, mocker, capsys) -> None:
def test_rename_value_fail(test_application, mocker, capsys) -> None:
"""Test renaming an inline tag."""
app = test_application
app.load_vault()
app._load_vault()
mocker.patch(
"obsidian_metadata.models.application.Questions.ask_application_main",
side_effect=["rename_metadata", KeyError],
@@ -425,7 +461,7 @@ def test_rename_value_fail(test_application, mocker, capsys) -> None:
def test_review_no_changes(test_application, mocker, capsys) -> None:
"""Review changes when no changes to vault."""
app = test_application
app.load_vault()
app._load_vault()
mocker.patch(
"obsidian_metadata.models.application.Questions.ask_application_main",
side_effect=["review_changes", KeyError],
@@ -439,7 +475,7 @@ def test_review_no_changes(test_application, mocker, capsys) -> None:
def test_review_changes(test_application, mocker, capsys) -> None:
"""Review changes when no changes to vault."""
app = test_application
app.load_vault()
app._load_vault()
mocker.patch(
"obsidian_metadata.models.application.Questions.ask_application_main",
side_effect=["rename_metadata", "review_changes", KeyError],
@@ -471,7 +507,7 @@ def test_review_changes(test_application, mocker, capsys) -> None:
def test_vault_backup(test_application, mocker, capsys) -> None:
"""Test backing up a vault."""
app = test_application
app.load_vault()
app._load_vault()
mocker.patch(
"obsidian_metadata.models.application.Questions.ask_application_main",
side_effect=["vault_actions", KeyError],
@@ -492,7 +528,7 @@ def test_vault_delete(test_application, mocker, capsys, tmp_path) -> None:
app = test_application
backup_path = Path(tmp_path / "application.bak")
backup_path.mkdir()
app.load_vault()
app._load_vault()
mocker.patch(
"obsidian_metadata.models.application.Questions.ask_application_main",
side_effect=["vault_actions", KeyError],

View File

@@ -6,13 +6,14 @@ author:: John Doe
status:: new
type:: book
type:: article
#food/fruit/apple
#food/fruit/pear
#dinner #lunch #breakfast
on_one_note:: one
#food/fruit/apple
#food/fruit/pear
#dinner #lunch #breakfast
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?
At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat.
At vero eos et accusamus et iusto odio dignissimos ducimus qui blanditiis praesentium voluptatum deleniti atque corrupti quos dolores et quas molestias excepturi sint occaecati cupiditate non provident, similique sunt in culpa qui officia deserunt mollitia animi, id est laborum et dolorum fuga. Et harum quidem rerum facilis est et expedita distinctio. Nam libero tempore, cum soluta nobis est eligendi optio cumque nihil impedit quo minus id quod maxime placeat facere possimus, omnis voluptas assumenda est, omnis dolor repellendus. Temporibus autem quibusdam et aut officiis debitis aut rerum necessitatibus saepe eveniet ut et voluptates repudiandae sint et molestiae non recusandae. Itaque earum rerum hic tenetur a sapiente delectus, ut aut reiciendis voluptatibus maiores alias consequatur aut perferendis doloribus asperiores repellat.

View File

@@ -69,7 +69,7 @@ def test_vault_metadata(capsys) -> None:
vm = VaultMetadata()
assert vm.dict == {}
vm.add_metadata(METADATA)
vm.index_metadata(METADATA)
assert vm.dict == {
"frontmatter_Key1": ["author name"],
"frontmatter_Key2": ["article", "note"],
@@ -97,7 +97,7 @@ def test_vault_metadata(capsys) -> None:
assert captured.out == Regex(r"│ frontmatter_Key1 +│ author name +│")
new_metadata = {"added_key": ["added_value"], "frontmatter_Key2": ["new_value"]}
vm.add_metadata(new_metadata)
vm.index_metadata(new_metadata)
assert vm.dict == {
"added_key": ["added_value"],
"frontmatter_Key1": ["author name"],
@@ -115,7 +115,7 @@ def test_vault_metadata(capsys) -> None:
def test_vault_metadata_contains() -> None:
"""Test contains method."""
vm = VaultMetadata()
vm.add_metadata(METADATA)
vm.index_metadata(METADATA)
assert vm.dict == {
"frontmatter_Key1": ["author name"],
"frontmatter_Key2": ["article", "note"],
@@ -141,7 +141,7 @@ def test_vault_metadata_contains() -> None:
def test_vault_metadata_delete() -> None:
"""Test delete method."""
vm = VaultMetadata()
vm.add_metadata(METADATA)
vm.index_metadata(METADATA)
assert vm.dict == {
"frontmatter_Key1": ["author name"],
"frontmatter_Key2": ["article", "note"],
@@ -165,7 +165,7 @@ def test_vault_metadata_delete() -> None:
def test_vault_metadata_rename() -> None:
"""Test rename method."""
vm = VaultMetadata()
vm.add_metadata(METADATA)
vm.index_metadata(METADATA)
assert vm.dict == {
"frontmatter_Key1": ["author name"],
"frontmatter_Key2": ["article", "note"],

View File

@@ -60,6 +60,14 @@ def test_validate_new_tag() -> None:
assert questions._validate_new_tag("new_tag") is True
def test_validate_number() -> None:
"""Test number validation."""
questions = Questions(vault=VAULT)
assert "Must be an integer" in questions._validate_number("test")
assert "Must be an integer" in questions._validate_number("1.1")
assert questions._validate_number("1") is True
def test_validate_existing_inline_tag() -> None:
"""Test existing tag validation."""
questions = Questions(vault=VAULT)
@@ -80,12 +88,10 @@ def test_validate_key_exists_regex() -> None:
def test_validate_value() -> None:
"""Test value validation."""
questions = Questions(vault=VAULT)
assert questions._validate_value("test") is True
assert "Value cannot be empty" in questions._validate_value("")
assert questions._validate_value("test") is True
questions2 = Questions(vault=VAULT, key="frontmatter_Key1")
assert questions2._validate_value("test") == "frontmatter_Key1:test does not exist"
assert "Value cannot be empty" in questions2._validate_value("")
assert questions2._validate_value("author name") is True

View File

@@ -4,7 +4,7 @@
from pathlib import Path
from obsidian_metadata._config import Config
from obsidian_metadata.models import Vault
from obsidian_metadata.models import Vault, VaultFilter
from obsidian_metadata.models.enums import MetadataType
from tests.helpers import Regex
@@ -20,7 +20,7 @@ def test_vault_creation(test_vault):
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() == 3
assert len(vault.all_notes) == 3
assert vault.metadata.dict == {
"Inline Tags": [
@@ -64,16 +64,36 @@ def test_get_filtered_notes(sample_vault) -> None:
vault_path = sample_vault
config = Config(config_path="tests/fixtures/sample_vault_config.toml", vault_path=vault_path)
vault_config = config.vaults[0]
vault = Vault(config=vault_config, path_filter="front")
assert vault.num_notes() == 4
filters = [VaultFilter(path_filter="front")]
vault = Vault(config=vault_config, filters=filters)
assert len(vault.all_notes) == 13
assert len(vault.notes_in_scope) == 4
vault_path = sample_vault
config = Config(config_path="tests/fixtures/sample_vault_config.toml", vault_path=vault_path)
vault_config = config.vaults[0]
vault2 = Vault(config=vault_config, path_filter="mixed")
filters = [VaultFilter(path_filter="mixed")]
vault = Vault(config=vault_config, filters=filters)
assert len(vault.all_notes) == 13
assert len(vault.notes_in_scope) == 1
assert vault2.num_notes() == 1
filters = [VaultFilter(key_filter="on_one_note")]
vault = Vault(config=vault_config, filters=filters)
assert len(vault.all_notes) == 13
assert len(vault.notes_in_scope) == 1
filters = [VaultFilter(key_filter="type", value_filter="book")]
vault = Vault(config=vault_config, filters=filters)
assert len(vault.all_notes) == 13
assert len(vault.notes_in_scope) == 10
filters = [VaultFilter(tag_filter="brunch")]
vault = Vault(config=vault_config, filters=filters)
assert len(vault.all_notes) == 13
assert len(vault.notes_in_scope) == 1
filters = [VaultFilter(tag_filter="brunch"), VaultFilter(path_filter="inbox")]
vault = Vault(config=vault_config, filters=filters)
assert len(vault.all_notes) == 13
assert len(vault.notes_in_scope) == 0
def test_backup(test_vault, capsys):