From e269c0b8d8d73de0b9b585fefff8c808fb1a0f5d Mon Sep 17 00:00:00 2001 From: Research Assistant Date: Mon, 1 Jun 2026 11:16:53 +0800 Subject: [PATCH] fix: correct base filter syntax to Obsidian standard (== and double quotes) --- paperforge/worker/base_views.py | 12 ++++---- tests/test_base_views.py | 50 +++++++++++++++++++++++++++++---- 2 files changed, 50 insertions(+), 12 deletions(-) diff --git a/paperforge/worker/base_views.py b/paperforge/worker/base_views.py index 56f43f05..0c9df258 100644 --- a/paperforge/worker/base_views.py +++ b/paperforge/worker/base_views.py @@ -74,7 +74,7 @@ def build_base_views(domain: str) -> list[dict]: { "name": "待 OCR", "order": ["year", "first_author", "title", "has_pdf", "do_ocr", "ocr_status", "pdf_path"], - "filter": "do_ocr = true AND ocr_status = 'pending'", + "filter": 'do_ocr == true && ocr_status == "pending"', }, { "name": "待深度阅读", @@ -89,12 +89,12 @@ def build_base_views(domain: str) -> list[dict]: "deep_reading_status", "pdf_path", ], - "filter": "analyze = true AND ocr_status = 'done' AND deep_reading_status = 'pending'", + "filter": 'analyze == true && ocr_status == "done" && deep_reading_status == "pending"', }, { "name": "重做OCR", "order": ["year", "first_author", "title", "ocr_redo", "ocr_status"], - "filter": "ocr_status = 'done'", + "filter": 'ocr_status == "done"', }, ] @@ -137,7 +137,7 @@ def _render_views_section(views: list[dict]) -> str: for col in v["order"]: lines.append(f" - {col}") if v["filter"]: - lines.append(f' filter: "{v["filter"]}"') + lines.append(f" filter: '{v['filter']}'") if v.get("sort"): lines.append(" sort:") for sort_item in v["sort"]: @@ -285,7 +285,7 @@ views: for col in v["order"]: rendered += f" - {col}\n" if v["filter"]: - rendered += f' filter: "{v["filter"]}"\n' + rendered += f" filter: '{v['filter']}'\n" else: rendered += "\n" if v.get("sort"): @@ -377,7 +377,7 @@ def _build_base_yaml(folder_filter: str, views: list[dict]) -> str: for col in v["order"]: views_yaml += f" - {col}\n" if v["filter"]: - views_yaml += f' filter: "{v["filter"]}"\n' + views_yaml += f" filter: '{v['filter']}'\n" else: views_yaml += "\n" if v.get("sort"): diff --git a/tests/test_base_views.py b/tests/test_base_views.py index 4af5f1fe..20c90976 100644 --- a/tests/test_base_views.py +++ b/tests/test_base_views.py @@ -18,7 +18,7 @@ class TestBuildBaseViews: redo = next(v for v in views if v["name"] == "重做OCR") assert redo["order"] == ["year", "first_author", "title", "ocr_redo", "ocr_status"] - assert redo["filter"] == "ocr_status = 'done'" + assert redo["filter"] == 'ocr_status == "done"' def test_returns_exactly_4_views(self): views = build_base_views("骨科") @@ -41,15 +41,15 @@ class TestBuildBaseViews: def test_pending_ocr_filter(self): views = build_base_views("骨科") pending = next(v for v in views if v["name"] == "待 OCR") - assert "do_ocr = true" in pending["filter"] - assert "ocr_status = 'pending'" in pending["filter"] + assert "do_ocr == true" in pending["filter"] + assert 'ocr_status == "pending"' in pending["filter"] def test_deep_reading_pending_filter(self): views = build_base_views("骨科") pending = next(v for v in views if v["name"] == "待深度阅读") - assert "analyze = true" in pending["filter"] - assert "ocr_status = 'done'" in pending["filter"] - assert "deep_reading_status = 'pending'" in pending["filter"] + assert "analyze == true" in pending["filter"] + assert 'ocr_status == "done"' in pending["filter"] + assert 'deep_reading_status == "pending"' in pending["filter"] def test_removed_views_not_present(self): removed = {"推荐分析", "OCR 完成", "深度阅读完成", "正式卡片", "全记录"} @@ -111,3 +111,41 @@ class TestSubstituteConfigPlaceholders: lit.mkdir() result = substitute_config_placeholders(content, {"literature": lit, "vault": vault}) assert "\\" not in result # Should use forward slash + + +class TestBaseFilterYAMLSyntax: + """Verify generated Base YAML uses correct Obsidian Base filter syntax.""" + + def test_all_filters_use_double_equals_and_double_quotes(self): + """Filter values must use == and " for strings, per Obsidian Base docs.""" + views = build_base_views("test") + for v in views: + if not v["filter"]: + continue + f = v["filter"] + assert "=" not in f.replace("==", ""), f"single = in filter: {f}" + assert "'" not in f, f"single quote in filter: {f}" + + def test_rendered_yaml_wraps_filter_in_single_quotes(self): + """YAML output must wrap filter strings in single quotes to protect double quotes inside.""" + from paperforge.worker.base_views import _render_views_section + + views = build_base_views("test") + yaml_output = _render_views_section(views) + + for v in views: + if not v["filter"]: + continue + expected_line = f" filter: '{v['filter']}'" + assert expected_line in yaml_output, ( + f"Expected YAML line not found: {expected_line}\nGot:\n{yaml_output}" + ) + + def test_no_single_quote_in_rendered_filter_value(self): + """No single quote inside the filter value (would break YAML single-quote wrapping).""" + views = build_base_views("test") + for v in views: + if not v["filter"]: + continue + inner = v["filter"] + assert "'" not in inner, f"single quote inside filter value: {inner}"