#!/usr/bin/env python3 from __future__ import annotations import argparse import json import re import sys import tomllib from pathlib import Path from typing import Any DEFAULT_ROOT = Path(__file__).resolve().parents[2] SEMVER_RE = re.compile(r"^\d+\.\d+\.\d+$") LAUNCHER_PREFIX_RE = re.compile(r"^[a-z]+$") DESCRIPTION_MAX_CHARS = 120 ALLOWED_TAGS = { "ai", "animation", "arch", "audio", "bar", "clock", "countdown", "demo", "debian", "desktop", "development", "emoticon", "fedora", "fun", "gaming", "gentoo", "hardware", "hyprland", "indicator", "labwc", "language", "launcher", "mangowc", "media", "music", "network", "niri", "nixos", "opensuse", "panel", "privacy", "productivity", "recording", "service", "shortcut", "sway", "system", "theming", "time", "utility", "video", "void", "wallpaper", } # An id segment must be a lowercase flat identifier: the part after the "/" is also the # plugin's directory here, its export directory on disk, and its slug on the website. ID_SEGMENT_RE = re.compile(r"^[a-z0-9][a-z0-9._-]*$") # Names the website reserves for its own routes; a plugin folder cannot take one. RESERVED_NAMES = {"license", "readme", "index", "api", "admin", "static", "assets"} # Files every published plugin ships: the site renders the README as the plugin page and # the thumbnail as its card, and the English catalog backs every label_key. REQUIRED_PLUGIN_FILES = ("README.md", "thumbnail.webp", "translations/en.json") THUMBNAIL_MAX_BYTES = 512 * 1024 # The store and the website lay out plugin cards on a fixed 16:9 grid, so the thumbnail # generator exports exactly this. THUMBNAIL_SIZE = (960, 540) THUMBNAIL_GENERATOR_URL = "https://assets.noctalia.dev/plugins/thumbnail-generator.html" WEBP_MAGIC_PREFIX = b"RIFF" WEBP_MAGIC_FORMAT = b"WEBP" # Enough of the file to cover the RIFF header plus the first chunk header and the widest # dimension field of any WebP variant. WEBP_HEADER_BYTES = 32 ROOT_STRING_FIELDS = ( "id", "name", "version", "author", "license", "icon", "description", ) ROOT_ARRAY_FIELDS = ("dependencies", "tags") ENTRY_TYPES = ("widget", "panel", "shortcut", "desktop_widget", "launcher_provider", "service") SETTING_OWNER_TYPES = ("widget", "panel", "desktop_widget", "launcher_provider") SETTING_TYPES = {"string", "string_list", "bool", "glyph", "select", "folder", "file", "int", "color"} PANEL_PLACEMENTS = {"attached", "floating"} PANEL_POSITIONS = { "auto", "center", "top_left", "top_center", "top_right", "center_left", "center_right", "bottom_left", "bottom_center", "bottom_right", } ROOT_FIELDS = set(ROOT_STRING_FIELDS) | set(ROOT_ARRAY_FIELDS) | set(ENTRY_TYPES) | { "setting", "deprecated", "plugin_api", } BASE_ENTRY_FIELDS = {"id", "entry"} ENTRY_FIELDS = { "widget": BASE_ENTRY_FIELDS | {"setting"}, "panel": BASE_ENTRY_FIELDS | {"setting", "width", "height", "placement", "position", "open_near_click"}, "desktop_widget": BASE_ENTRY_FIELDS | {"setting"}, "service": BASE_ENTRY_FIELDS, "shortcut": BASE_ENTRY_FIELDS, "launcher_provider": BASE_ENTRY_FIELDS | {"prefix", "glyph", "include_in_global_search", "debounce_ms", "setting", "category"}, } CATEGORY_FIELDS = {"label", "glyph"} SETTING_FIELDS = { "key", "type", "label_key", "description_key", "default", "options", "min", "max", "step", "visible_when", "advanced", } OPTION_FIELDS = {"value", "label_key"} VISIBLE_WHEN_FIELDS = {"key", "values"} # Raw HTML is not supported on plugin pages. Markdown autolinks such as # do not match this expression. HTML_RE = re.compile( r"