2023-09-24 18:16:15 +02:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
2024-09-11 12:07:09 +02:00
|
|
|
import argparse
|
2023-09-24 18:16:15 +02:00
|
|
|
import json
|
2023-01-20 17:33:33 +01:00
|
|
|
import sys
|
2024-03-13 11:34:07 +01:00
|
|
|
from pathlib import Path
|
2024-02-07 14:49:55 +01:00
|
|
|
from difflib import SequenceMatcher
|
2023-09-24 18:16:15 +02:00
|
|
|
from typing import Any, Dict, Generator, List, Tuple
|
|
|
|
|
|
|
|
import jsonschema
|
2024-09-11 12:07:09 +02:00
|
|
|
import appslib.get_apps_repo as get_apps_repo
|
2024-03-11 17:34:33 +01:00
|
|
|
from appslib.utils import (
|
2024-09-11 12:07:09 +02:00
|
|
|
get_antifeatures, # pylint: disable=import-error
|
2024-03-11 17:34:33 +01:00
|
|
|
get_catalog,
|
|
|
|
get_categories,
|
|
|
|
get_graveyard,
|
|
|
|
get_wishlist,
|
|
|
|
)
|
2023-12-18 19:00:53 +01:00
|
|
|
|
|
|
|
|
2024-09-11 12:07:09 +02:00
|
|
|
def validate_schema(data: dict, schema_path: Path) -> List[str]:
|
2024-03-13 11:34:07 +01:00
|
|
|
schema = json.load(schema_path.open("r", encoding="utf-8"))
|
|
|
|
validator = jsonschema.Draft202012Validator(schema)
|
2024-09-11 12:07:09 +02:00
|
|
|
return [
|
|
|
|
f"at .{'.'.join(error.path)}: {error.message}"
|
|
|
|
for error in validator.iter_errors(data)
|
|
|
|
]
|
2023-09-24 18:16:15 +02:00
|
|
|
|
|
|
|
|
2024-09-11 12:07:09 +02:00
|
|
|
def validate_schema_pretty(apps_path: Path, data: dict, name: str) -> bool:
|
|
|
|
schema_path = apps_path / "schemas" / f"{name}.toml.schema.json"
|
2024-03-13 11:34:07 +01:00
|
|
|
schema_errors = list(validate_schema(data, schema_path))
|
|
|
|
if schema_errors:
|
|
|
|
print(f"Error while validating {name} against schema:")
|
2024-09-11 12:07:09 +02:00
|
|
|
for error in schema_errors:
|
|
|
|
print(f" - {error}")
|
2024-03-13 11:34:07 +01:00
|
|
|
print()
|
2024-09-11 12:07:09 +02:00
|
|
|
return bool(schema_errors)
|
2024-03-13 11:34:07 +01:00
|
|
|
|
|
|
|
|
2024-03-11 17:34:33 +01:00
|
|
|
def check_app(
|
|
|
|
app: str, infos: Dict[str, Any]
|
|
|
|
) -> Generator[Tuple[str, bool], None, None]:
|
2023-02-09 17:29:31 +01:00
|
|
|
if "state" not in infos:
|
2023-09-24 22:12:11 +02:00
|
|
|
yield "state is missing", True
|
2023-09-24 18:16:15 +02:00
|
|
|
return
|
|
|
|
|
2023-09-25 15:41:07 +02:00
|
|
|
# validate that the app is not (anymore?) in the wishlist
|
|
|
|
# we use fuzzy matching because the id in catalog may not be the same exact id as in the wishlist
|
|
|
|
# some entries are ignore-hard-coded, because e.g. radarr an readarr are really different apps...
|
|
|
|
ignored_wishlist_entries = ["readarr"]
|
|
|
|
wishlist_matches = [
|
|
|
|
wish
|
|
|
|
for wish in get_wishlist()
|
|
|
|
if wish not in ignored_wishlist_entries
|
|
|
|
and SequenceMatcher(None, app, wish).ratio() > 0.9
|
|
|
|
]
|
|
|
|
if wishlist_matches:
|
|
|
|
yield f"app seems to be listed in wishlist: {wishlist_matches}", True
|
|
|
|
|
2023-12-18 19:03:23 +01:00
|
|
|
ignored_graveyard_entries = ["mailman"]
|
2023-12-18 19:00:53 +01:00
|
|
|
graveyard_matches = [
|
|
|
|
grave
|
|
|
|
for grave in get_graveyard()
|
2023-12-18 19:03:23 +01:00
|
|
|
if grave not in ignored_graveyard_entries
|
|
|
|
and SequenceMatcher(None, app, grave).ratio() > 0.9
|
2023-12-18 19:00:53 +01:00
|
|
|
]
|
|
|
|
if graveyard_matches:
|
|
|
|
yield f"app seems to be listed in graveyard: {graveyard_matches}", True
|
|
|
|
|
2023-09-24 18:16:15 +02:00
|
|
|
repo_name = infos.get("url", "").split("/")[-1]
|
|
|
|
if repo_name != f"{app}_ynh":
|
2023-09-24 20:30:04 +02:00
|
|
|
yield f"repo name should be {app}_ynh, not in {repo_name}", True
|
2023-09-24 18:16:15 +02:00
|
|
|
|
|
|
|
antifeatures = infos.get("antifeatures", [])
|
|
|
|
for antifeature in antifeatures:
|
|
|
|
if antifeature not in get_antifeatures():
|
2023-09-24 20:30:04 +02:00
|
|
|
yield f"unknown antifeature {antifeature}", True
|
2023-09-24 18:16:15 +02:00
|
|
|
|
|
|
|
category = infos.get("category")
|
|
|
|
if not category:
|
2023-09-24 20:30:04 +02:00
|
|
|
yield "category is missing", True
|
2023-09-24 18:16:15 +02:00
|
|
|
else:
|
|
|
|
if category not in get_categories():
|
2023-09-24 20:30:04 +02:00
|
|
|
yield f"unknown category {category}", True
|
2023-02-09 17:29:31 +01:00
|
|
|
|
2023-09-24 18:16:15 +02:00
|
|
|
subtags = infos.get("subtags", [])
|
|
|
|
for subtag in subtags:
|
2023-09-24 20:30:04 +02:00
|
|
|
if subtag not in get_categories().get(category, {}).get("subtags", []):
|
|
|
|
yield f"unknown subtag {category} / {subtag}", False
|
2023-01-20 17:33:33 +01:00
|
|
|
|
2023-02-09 17:29:31 +01:00
|
|
|
|
2024-09-11 12:07:09 +02:00
|
|
|
def check_all_apps() -> bool:
|
|
|
|
has_errors = False
|
2023-09-24 18:16:15 +02:00
|
|
|
for app, info in get_catalog().items():
|
|
|
|
errors = list(check_app(app, info))
|
|
|
|
if errors:
|
2024-09-11 12:07:09 +02:00
|
|
|
print(f"{app}:")
|
|
|
|
for error, is_fatal in errors:
|
|
|
|
if is_fatal:
|
|
|
|
has_errors = True
|
|
|
|
level = "error" if is_fatal else "warning"
|
|
|
|
print(f" - {level}: {error}")
|
|
|
|
return has_errors
|
2023-01-20 17:33:33 +01:00
|
|
|
|
|
|
|
|
2023-09-24 18:16:15 +02:00
|
|
|
def main() -> None:
|
2024-09-11 12:07:09 +02:00
|
|
|
parser = argparse.ArgumentParser()
|
|
|
|
get_apps_repo.add_args(parser)
|
|
|
|
args = parser.parse_args()
|
|
|
|
apps_path = get_apps_repo.from_args(args)
|
|
|
|
|
2023-09-24 18:16:15 +02:00
|
|
|
has_errors = False
|
2023-01-20 17:33:33 +01:00
|
|
|
|
2024-09-11 12:07:09 +02:00
|
|
|
has_errors |= validate_schema_pretty(apps_path, get_antifeatures(), "antifeatures")
|
|
|
|
has_errors |= validate_schema_pretty(apps_path, get_catalog(), "apps")
|
|
|
|
has_errors |= validate_schema_pretty(apps_path, get_categories(), "categories")
|
|
|
|
has_errors |= validate_schema_pretty(apps_path, get_graveyard(), "graveyard")
|
|
|
|
has_errors |= validate_schema_pretty(apps_path, get_wishlist(), "wishlist")
|
2023-01-20 17:33:33 +01:00
|
|
|
|
2024-09-11 12:07:09 +02:00
|
|
|
has_errors |= check_all_apps()
|
2023-02-09 17:29:31 +01:00
|
|
|
|
2024-03-13 11:34:07 +01:00
|
|
|
sys.exit(has_errors)
|
2023-01-20 17:33:33 +01:00
|
|
|
|
|
|
|
|
2023-09-24 18:16:15 +02:00
|
|
|
if __name__ == "__main__":
|
|
|
|
main()
|