feat: add recommended flag with priority sorting
- Add `recommended` field to AddonInfo model (omitted from output when false) - Rename `_load_repo_tags` to `_load_repo_config` to load all addon metadata - Sort output: recommended addons first, then by stars descending - Mark 5 initial recommended addons: zotero-pdf-translate, zotero-better-notes, zotero-better-bibtex, jasminum, paper-chat-for-zotero - Add 4 test cases for recommended field serialization and deserialization Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
21bd6bd625
commit
adcb028cfe
@ -1 +1 @@
|
|||||||
{"tags": ["metadata"]}
|
{"tags": ["metadata"], "recommended": true}
|
||||||
@ -1 +1 @@
|
|||||||
{"tags": ["integration"]}
|
{"tags": ["integration"], "recommended": true}
|
||||||
@ -1 +1 @@
|
|||||||
{"tags": ["ai"]}
|
{"tags": ["ai"], "recommended": true}
|
||||||
@ -1 +1 @@
|
|||||||
{"tags": ["notes"]}
|
{"tags": ["notes"], "recommended": true}
|
||||||
@ -1 +1 @@
|
|||||||
{"tags": ["reader"]}
|
{"tags": ["reader"], "recommended": true}
|
||||||
@ -156,6 +156,7 @@ class AddonInfo:
|
|||||||
stars: Optional[int] = None
|
stars: Optional[int] = None
|
||||||
author: Author = field(default_factory=Author)
|
author: Author = field(default_factory=Author)
|
||||||
tags: list[str] = field(default_factory=list)
|
tags: list[str] = field(default_factory=list)
|
||||||
|
recommended: bool = False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def owner(self) -> Optional[str]:
|
def owner(self) -> Optional[str]:
|
||||||
@ -193,6 +194,8 @@ class AddonInfo:
|
|||||||
result["author"] = author_dict
|
result["author"] = author_dict
|
||||||
if self.tags:
|
if self.tags:
|
||||||
result["tags"] = self.tags
|
result["tags"] = self.tags
|
||||||
|
if self.recommended:
|
||||||
|
result["recommended"] = True
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -219,4 +222,5 @@ class AddonInfo:
|
|||||||
stars=data.get("stars"),
|
stars=data.get("stars"),
|
||||||
author=author,
|
author=author,
|
||||||
tags=data.get("tags", []),
|
tags=data.get("tags", []),
|
||||||
|
recommended=data.get("recommended", False),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -72,8 +72,10 @@ class CacheScraper:
|
|||||||
for url in self.config.previous_info_urls:
|
for url in self.config.previous_info_urls:
|
||||||
result = self.fallback.apply_fallback(result, url)
|
result = self.fallback.apply_fallback(result, url)
|
||||||
|
|
||||||
# Sort by stars (descending)
|
# Sort: recommended first, then by stars (descending)
|
||||||
result.sort(key=lambda x: x.get("stars") or 0, reverse=True)
|
result.sort(
|
||||||
|
key=lambda x: (not x.get("recommended", False), -(x.get("stars") or 0))
|
||||||
|
)
|
||||||
|
|
||||||
# Save output
|
# Save output
|
||||||
self._save_output(result)
|
self._save_output(result)
|
||||||
@ -102,25 +104,24 @@ class CacheScraper:
|
|||||||
|
|
||||||
return repos
|
return repos
|
||||||
|
|
||||||
def _load_repo_tags(self, repo: str) -> list[str]:
|
def _load_repo_config(self, repo: str) -> dict:
|
||||||
"""Load tags for a repository from its input file.
|
"""Load metadata for a repository from its input file.
|
||||||
|
|
||||||
The file may contain JSON like {"tags": ["ai", "notes"]} or be empty.
|
The file may contain JSON like {"tags": ["ai"], "recommended": true} or be empty.
|
||||||
Returns empty list if no tags are defined.
|
Returns empty dict if no metadata is defined.
|
||||||
"""
|
"""
|
||||||
filename = repo.replace("/", "@", 1)
|
filename = repo.replace("/", "@", 1)
|
||||||
config_file = self.config.input_dir / filename
|
config_file = self.config.input_dir / filename
|
||||||
if not config_file.exists():
|
if not config_file.exists():
|
||||||
return []
|
return {}
|
||||||
try:
|
try:
|
||||||
content = config_file.read_text(encoding="utf-8").strip()
|
content = config_file.read_text(encoding="utf-8").strip()
|
||||||
if not content:
|
if not content:
|
||||||
return []
|
return {}
|
||||||
data = json.loads(content)
|
return json.loads(content)
|
||||||
return data.get("tags", [])
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug(f"Failed to parse tags from {config_file}: {e}")
|
logger.debug(f"Failed to parse config from {config_file}: {e}")
|
||||||
return []
|
return {}
|
||||||
|
|
||||||
def _process_repo(self, repo: str) -> Optional[AddonInfo]:
|
def _process_repo(self, repo: str) -> Optional[AddonInfo]:
|
||||||
"""Process a single repository using cache.
|
"""Process a single repository using cache.
|
||||||
@ -153,11 +154,13 @@ class CacheScraper:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
# Create AddonInfo
|
# Create AddonInfo
|
||||||
|
repo_config = self._load_repo_config(repo)
|
||||||
addon_info = AddonInfo(
|
addon_info = AddonInfo(
|
||||||
repo=repo,
|
repo=repo,
|
||||||
releases=releases,
|
releases=releases,
|
||||||
name=name,
|
name=name,
|
||||||
tags=self._load_repo_tags(repo),
|
tags=repo_config.get("tags", []),
|
||||||
|
recommended=repo_config.get("recommended", False),
|
||||||
)
|
)
|
||||||
|
|
||||||
# Fetch author info
|
# Fetch author info
|
||||||
|
|||||||
@ -211,3 +211,27 @@ class TestAddonInfo:
|
|||||||
data = {"repo": "owner/repo"}
|
data = {"repo": "owner/repo"}
|
||||||
addon = AddonInfo.from_dict(data)
|
addon = AddonInfo.from_dict(data)
|
||||||
assert addon.tags == []
|
assert addon.tags == []
|
||||||
|
|
||||||
|
def test_to_dict_includes_recommended_when_true(self):
|
||||||
|
"""Test to_dict includes recommended field only when True."""
|
||||||
|
addon = AddonInfo(repo="owner/repo", recommended=True)
|
||||||
|
result = addon.to_dict()
|
||||||
|
assert result["recommended"] is True
|
||||||
|
|
||||||
|
def test_to_dict_omits_recommended_when_false(self):
|
||||||
|
"""Test to_dict omits recommended key when False (backward compat)."""
|
||||||
|
addon = AddonInfo(repo="owner/repo", recommended=False)
|
||||||
|
result = addon.to_dict()
|
||||||
|
assert "recommended" not in result
|
||||||
|
|
||||||
|
def test_from_dict_parses_recommended(self):
|
||||||
|
"""Test from_dict parses recommended field."""
|
||||||
|
data = {"repo": "owner/repo", "recommended": True}
|
||||||
|
addon = AddonInfo.from_dict(data)
|
||||||
|
assert addon.recommended is True
|
||||||
|
|
||||||
|
def test_from_dict_recommended_defaults_to_false(self):
|
||||||
|
"""Test from_dict defaults recommended to False when key absent."""
|
||||||
|
data = {"repo": "owner/repo"}
|
||||||
|
addon = AddonInfo.from_dict(data)
|
||||||
|
assert addon.recommended is False
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user