diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index a0d691d..67ee70a 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -31,7 +31,12 @@ "ms-python.python", "njpwerner.autodocstring", "tamasfe.even-better-toml", - ] + ], + "settings": { + // Pylance doesn't understand the pydantic types and generates lots of + // false positive warnings. + "python.analysis.typeCheckingMode": "off", + } } }, diff --git a/flatpaker.schema.json b/flatpaker.schema.json index de7d66d..66fe146 100644 --- a/flatpaker.schema.json +++ b/flatpaker.schema.json @@ -1,240 +1,356 @@ { - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://github.com.com/dcbaker/flatpaker/flatpaker.schema.json", - "title": "flatpaker", - "description": "A description of a Ren'py or RPGMaker game to package as a Flatpak", - "type": "object", - "properties": { - "common": { - "description": "Common properties used in the flatpak, appdata, and desktop files.", - "type": "object", + "$defs": { + "AppData": { + "description": "The appdata section of the build toml description.", "properties": { - "name": { - "type": "string", - "description": "The name of the application in proper casing." + "summary": { + "description": "A short description of the game", + "title": "Application summary", + "type": "string" + }, + "description": { + "description": "A longer description of the game.", + "title": "Application description", + "type": "string" + }, + "content_rating": { + "additionalProperties": { + "enum": [ + "none", + "mild", + "moderate", + "intense" + ], + "type": "string" + }, + "description": "Content information for the current game.", + "propertyNames": { + "enum": [ + "drugs-alcohol", + "drugs-narcotics", + "drugs-tobacco", + "language-descrimination", + "language-humor", + "language-profanity", + "money-gambling", + "money-purchasing", + "sex-nudity", + "sex-themes", + "social-audio", + "social-chat", + "social-contacts", + "social-info", + "social-location", + "violence-bloodshed", + "violence-cartoon", + "violence-fantasy", + "violence-realistic", + "violence-sexual" + ] + }, + "title": "OARS Content Rating Information", + "type": "object" + }, + "releases": { + "additionalProperties": { + "type": "string" + }, + "description": "Mapping of releases in the form `\"YYYY-MM-DD\" = \"X.Y.Z\"", + "title": "Release information for this game", + "type": "object" }, + "license": { + "default": "LicenseRef-Proprietary", + "description": "A valid SPDX license Expression for this game", + "title": "SPDX License Expression", + "type": "string" + } + }, + "required": [ + "summary", + "description" + ], + "title": "Application Metadata", + "type": "object" + }, + "Archive": { + "description": "An archive entry in the sources section of the build toml description.", + "properties": { + "path": { + "description": "A local path to the sources. Must be relative to this file, or absolute.", + "format": "path", + "title": "Path to source", + "type": "string" + }, + "sha256": { + "anyOf": [ + { + "maxLength": 64, + "minLength": 64, + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Hexedecimal representatio of the SHA256 of this file.", + "title": "SHA256 Hash" + }, + "commands": { + "description": "Shell commands are run immediately after copying this source to the build directory", + "items": { + "type": "string" + }, + "title": "Extra shell commands", + "type": "array" + }, + "strip_components": { + "default": 1, + "description": "number of path elements to strip from patches. Passed to `patch -p N`", + "title": "Patch strip argument", + "type": "integer" + } + }, + "required": [ + "path" + ], + "title": "An archive source", + "type": "object" + }, + "Common": { + "description": "The common section of the build toml description.", + "properties": { "reverse_url": { - "type": "string", - "description": "A reverse root url for the game. For example, com.github.User. The name will be appended automatically" + "description": "A reverse root URL for the game. The Game name will be automatically appended from the name field. This cannot be changed.", + "example": "com.github.user", + "title": "Reverse URL", + "type": "string" + }, + "name": { + "description": "The proper name with punctionation and spaces for this game", + "example": "Soldier's Quest", + "title": "Proper Name", + "type": "string" }, "engine": { - "type": "string", - "enum": ["renpy8", "renpy7", "renpy7-py3", "rpgmaker"], - "description": "What engine this game is for." + "description": "Which engine (and runtime) this game uses", + "enum": [ + "renpy8", + "renpy7", + "renpy7-py3", + "rpgmaker" + ], + "title": "Engine name", + "type": "string" }, "categories": { - "type": "array", - "description": "Valid categories for the game's desktop file. 'Game' is added automatically", + "description": "Valid categories for the game's .destkop file. 'Game' is added automatically", "items": { - "type": "string", "enum": [ - "Adult", "ActionGame", "AdventureGame", "ArcadeGame", "BoardGame", "BlocksGame", - "CardGame", "KidsGame", "LogicGame", "RolePlaying", "Shooter", "Simulation", - "SportsGame", "StrategyGame" - ] - } + "ActionGame", + "Adult", + "AdventureGame", + "Amusement", + "ArcadeGame", + "BlocksGame", + "BoardGame", + "CardGame", + "Emulator", + "KidsGame", + "LogicGame", + "RolePlaying", + "Shooter", + "Simulation", + "SportsGame", + "StrategyGame" + ], + "type": "string" + }, + "title": "Freedesktop Application Categories", + "type": "array" } }, "required": [ - "name", "reverse_url", + "name", "engine" ], - "additionalProperties": false + "title": "common", + "type": "object" }, - "appdata": { - "description": "Application metadata.", - "type": "object", + "File": { + "description": "A file entry in the sources section of the build toml description.", "properties": { - "summary": { - " type": "string" - }, - "description": { + "path": { + "description": "A local path to the sources. Must be relative to this file, or absolute.", + "format": "path", + "title": "Path to source", "type": "string" }, - "content_rating": { - "type": "object", - "properties": { - "drugs-alcohol": { - "$ref": "#/$defs/content_ratings" - }, - "drugs-tobacco": { - "$ref": "#/$defs/content_ratings" - }, - "language-discrimination": { - "$ref": "#/$defs/content_ratings" - }, - "language-humor": { - "$ref": "#/$defs/content_ratings" - }, - "language-profanity": { - "$ref": "#/$defs/content_ratings" - }, - "money-gambling": { - "$ref": "#/$defs/content_ratings" - }, - "money-purchasing": { - "$ref": "#/$defs/content_ratings" - }, - "sex-nudity": { - "$ref": "#/$defs/content_ratings" - }, - "sex-themes": { - "$ref": "#/$defs/content_ratings" - }, - "social-audio": { - "$ref": "#/$defs/content_ratings" + "sha256": { + "anyOf": [ + { + "maxLength": 64, + "minLength": 64, + "type": "string" }, - "social-chat": { - "$ref": "#/$defs/content_ratings" - }, - "social-contacts": { - "$ref": "#/$defs/content_ratings" - }, - "social-info": { - "$ref": "#/$defs/content_ratings" - }, - "social-location": { - "$ref": "#/$defs/content_ratings" - }, - "violence-bloodshed": { - "$ref": "#/$defs/content_ratings" - }, - "violence-cartoon": { - "$ref": "#/$defs/content_ratings" - }, - "violence-fantasy": { - "$ref": "#/$defs/content_ratings" - }, - "violence-realistic": { - "$ref": "#/$defs/content_ratings" - }, - "violence-sexual": { - "$ref": "#/$defs/content_ratings" + { + "type": "null" } - }, - "additionalProperties": false + ], + "default": null, + "description": "Hexedecimal representatio of the SHA256 of this file.", + "title": "SHA256 Hash" }, - "releases": { - "type": "object", - "description": "A list of releases in the from `version : date`. Date should be YYYY-MM-DD", - "additionalProperties": { - "type": "string" - } - }, - "license": { - "description": "An SPDX license expression. If unset will default to proprietary", + "dest": { + "default": "game", + "description": "Relative path to install this file to.", + "title": "Installation location", "type": "string" + }, + "commands": { + "description": "Shell commands are run immediately after copying this source to the build directory", + "items": { + "type": "string" + }, + "title": "Extra shell commands", + "type": "array" } }, "required": [ - "summary", - "description" + "path" ], - "additionalProperties": false + "title": "A single file type source", + "type": "object" }, - "sources": { - "description": "Optionally, sources for this description.", - "type": "object", + "Patch": { + "description": "A patch entry in the sources section of the build toml description.", "properties": { - "archives": { - "description": "A list of archives used to build the project", - "type": "array", - "items": { - "type": "object", - "properties": { - "path": { - "type": "string" - }, - "strip_components": { - "type": "number" - }, - "sha256": { - "type": "string", - "description": "Optionally, a sha256 checksum. This will be generated if not provided" - }, - "commands": { - "type": "array", - "items": "string" - } - }, - "required": [ - "path" - ], - "additionalProperties": false - } + "path": { + "description": "A local path to the sources. Must be relative to this file, or absolute.", + "format": "path", + "title": "Path to source", + "type": "string" }, - "files": { - "description": "Single file sources", - "type": "array", - "items": { - "type": "object", - "properties": { - "path": { - "type": "string", - "description": "The Path to the file" - }, - "dest": { - "type": "string", - "description": "Where to install the file. .rpy files are automatically merged into the game directory, other files likely need an explicit destination" - }, - "sha256": { - "type": "string", - "description": "Optionally, a sha256 checksum. This will be generated if not provided" - }, - "commands": { - "type": "array", - "items": "string" - } + "sha256": { + "anyOf": [ + { + "maxLength": 64, + "minLength": 64, + "type": "string" }, - "required": [ - "path" - ], - "additionalProperties": false - } + { + "type": "null" + } + ], + "default": null, + "description": "Hexedecimal representatio of the SHA256 of this file.", + "title": "SHA256 Hash" }, - "patches": { - "description": "Unix patch files to apply", - "type": "array", - "items": { - "type": "object", - "properties": { - "path": { - "type": "string" - }, - "strip_components": { - "type": "number" - } - }, - "required": [ - "path" - ], - "additionalProperties": false - } + "strip_components": { + "default": 1, + "description": "number of path elements to strip from patches. Passed to `patch -p N`", + "title": "Patch strip argument", + "type": "integer" } }, "required": [ - "archives" + "path" ], - "additionalProperties": false + "title": "A patch source", + "type": "object" }, - "quirks": { - "description": "Workarounds for the quirks in games that cannot be easily resolved without flatpaker support.", - "type": "object", + "Quirks": { + "description": "Quirks of this build that have built-in wokarounds.\n\nThere are a number of common issues, particularly in Ren'Py games that\nflatpaker provides build steps to deal with.", "properties": { "force_window_gui_icon": { - "description": "For Ren'Py only. Use the window_gui.png file for the icon instead of extracting from the .exe or icns files.", + "default": false, + "description": "Normally icons from a .exe or .icns if they are avilable, however, some games do not customize these icon sources but do provide a customized incon in the img directory. Setting this to true forces the use of that icon.", + "title": "Force use of img/windows_gui/icon.png", "type": "boolean" }, "x_configure_prologue": { - "description": "A shell snippet to be run before any of the automated build steps. Because sometimes you just need an escape hatch", - "type": "string" + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "deprecated": true, + "description": "Extra shell commands to run after unpacking sources. This has been replaced with the `commands` field in sources", + "title": "Commands to run after unpacking all sources" }, "x_renpy_archived_window_gui_icon": { - "description": "Extract a windows_gui.png icon from the named .rpa", - "type": "string" + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "deprecated": true, + "description": "Extract an icon from an .rpa icon. This is deprecated, and is now an alias for force_window_gui_icon", + "title": "Extract the icon from an RPA archive." } - } + }, + "title": "Quirks of this game to work around", + "type": "object" + }, + "Sources": { + "description": "The sources section of the build toml description.", + "properties": { + "archives": { + "description": "Archive type sources, such as .7z, .zip, .tar.gz, etc.", + "items": { + "$ref": "#/$defs/Archive" + }, + "title": "Archive Sources", + "type": "array" + }, + "patches": { + "description": "Patches to be applied after all archives are unpacked", + "items": { + "$ref": "#/$defs/Patch" + }, + "title": "Patch sources", + "type": "array" + }, + "files": { + "description": "extra files to add the build directory", + "items": { + "$ref": "#/$defs/File" + }, + "title": "source file", + "type": "array" + } + }, + "required": [ + "archives" + ], + "title": "The sources for this build", + "type": "object" + } + }, + "$id": "https://github.com/dcbaker/flatpaker/flatpaker.schema.json", + "description": "A flatpaker build description for a Ren'Py or RPGMaker MV or MZ game", + "properties": { + "common": { + "$ref": "#/$defs/Common" + }, + "appdata": { + "$ref": "#/$defs/AppData" + }, + "quirks": { + "$ref": "#/$defs/Quirks" + }, + "sources": { + "$ref": "#/$defs/Sources" } }, "required": [ @@ -242,17 +358,6 @@ "appdata", "sources" ], - "additionalProperties": false, - "$defs": { - "content_ratings": { - "type": "string", - "enum": [ - "unknown", - "none", - "mild", - "moderate", - "intense" - ] - } - } -} + "title": "flatpaker", + "type": "object" +} \ No newline at end of file diff --git a/flatpaker/actions/schema.py b/flatpaker/actions/schema.py new file mode 100644 index 0000000..813b5dd --- /dev/null +++ b/flatpaker/actions/schema.py @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: MIT +# Copyright © 2026 Dylan Baker + +"""Dump JSON schema from pydantic models.""" + +from __future__ import annotations + +import json +import pathlib +import sys + +from flatpaker.description import Description + + +def dump_schema(path: pathlib.Path) -> bool: + """Dump JSON schmea from Pydantic model.""" + # TODO: there are a load of exceptions that could be generated here and need + # to be caught and turned into failures + try: + # Ensure the directory exists before attempting to write the file + path.parent.mkdir(parents=True, exist_ok=True) + with path.open('w') as f: + json.dump(Description.model_json_schema(by_alias=True), f, indent=4) + except PermissionError: + print(f'Error: Failed to open {path!s} for writing', file=sys.stderr) + else: + return True + return False diff --git a/flatpaker/description.py b/flatpaker/description.py index 3754ae7..aa61cf5 100644 --- a/flatpaker/description.py +++ b/flatpaker/description.py @@ -12,7 +12,7 @@ import warnings import tomlkit -from pydantic import BaseModel, Field, field_validator, model_validator +from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator from flatpaker.util import sanitize_name, validate_name @@ -57,10 +57,38 @@ class Common(BaseModel): """The common section of the build toml description.""" - reverse_url: str - name: str - engine: EngineName - categories: list[Category] = Field(default_factory=list) + model_config = ConfigDict(title='common') + + reverse_url: str = Field( + title='Reverse URL', + json_schema_extra={ + 'description': ('A reverse root URL for the game. ' + 'The Game name will be automatically appended from the name field. ' + 'This cannot be changed.'), + 'example': 'com.github.user', + }, + ) + name: str = Field( + title='Proper Name', + json_schema_extra={ + 'description': 'The proper name with punctionation and spaces for this game', + 'example': "Soldier's Quest", + }, + ) + engine: EngineName = Field( + title='Engine name', + json_schema_extra={ + 'description': 'Which engine (and runtime) this game uses', + }, + ) + categories: list[Category] = Field( + default_factory=list, + title='Freedesktop Application Categories', + json_schema_extra={ + 'description': ("Valid categories for the game's .destkop file. " + "'Game' is added automatically"), + }, + ) @field_validator('reverse_url', mode='before') @classmethod @@ -77,11 +105,41 @@ def appid(self) -> str: class AppData(BaseModel): """The appdata section of the build toml description.""" - summary: str - description: str - content_rating: dict[ContentFields, ContentRating] = Field(default_factory=dict) - releases: dict[str, str] = Field(default_factory=dict) - license: str = 'LicenseRef-Proprietary' + model_config = ConfigDict(title='Application Metadata') + + summary: str = Field( + title='Application summary', + json_schema_extra={ + 'description': 'A short description of the game', + }, + ) + description: str = Field( + title='Application description', + json_schema_extra={ + 'description': 'A longer description of the game.', + }, + ) + content_rating: dict[ContentFields, ContentRating] = Field( + default_factory=dict, + title='OARS Content Rating Information', + json_schema_extra={ + 'description': 'Content information for the current game.', + }, + ) + releases: dict[str, str] = Field( + default_factory=dict, + title='Release information for this game', + json_schema_extra={ + 'description': 'Mapping of releases in the form `"YYYY-MM-DD" = "X.Y.Z"', + }, + ) + license: str = Field( + default='LicenseRef-Proprietary', + title='SPDX License Expression', + json_schema_extra={ + 'description': 'A valid SPDX license Expression for this game', + }, + ) @field_validator('releases', mode='after') @classmethod @@ -94,8 +152,21 @@ def _validate_field(cls, value: dict[str, str]) -> dict[str, str]: class _Source(BaseModel): """Shared base class for sources.""" - path: pathlib.Path - sha256: str | None = Field(None, min_length=64, max_length=64) + path: pathlib.Path = Field( + title='Path to source', + json_schema_extra={ + 'description': 'A local path to the sources. Must be relative to this file, or absolute.', + }, + ) + sha256: str | None = Field( + None, + min_length=64, + max_length=64, + title='SHA256 Hash', + json_schema_extra={ + 'description': 'Hexedecimal representatio of the SHA256 of this file.', + }, + ) @field_validator('path', mode='before') @classmethod @@ -119,37 +190,132 @@ def __validate_sha256(cls, v: str | None) -> str | None: # pylint: disable=W023 class File(_Source): """A file entry in the sources section of the build toml description.""" - dest: str = 'game' - commands: list[str] = Field(default_factory=list) + model_config = ConfigDict(title='A single file type source') + + dest: str = Field( + 'game', + title='Installation location', + json_schema_extra={ + 'description': 'Relative path to install this file to.', + }, + ) + commands: list[str] = Field( + default_factory=list, + title='Extra shell commands', + json_schema_extra={ + 'description': ('Shell commands are run immediately after copying this ' + 'source to the build directory'), + }, + ) class Patch(_Source): """A patch entry in the sources section of the build toml description.""" - strip_components: int = 1 + model_config = ConfigDict(title='A patch source') + + strip_components: int = Field( + 1, + title='Patch strip argument', + json_schema_extra={ + 'description': 'number of path elements to strip from patches. Passed to `patch -p N`', + }, + ) class Archive(_Source): """An archive entry in the sources section of the build toml description.""" - commands: list[str] = Field(default_factory=list) - strip_components: int = 1 + model_config = ConfigDict(title='An archive source') + + commands: list[str] = Field( + default_factory=list, + title='Extra shell commands', + json_schema_extra={ + 'description': ('Shell commands are run immediately after copying this ' + 'source to the build directory'), + }, + ) + strip_components: int = Field( + 1, + title='Patch strip argument', + json_schema_extra={ + 'description': 'number of path elements to strip from patches. Passed to `patch -p N`', + }, + ) class Sources(BaseModel): """The sources section of the build toml description.""" - archives: list[Archive] = Field(default_factory=list) - patches: list[Patch] = Field(default_factory=list) - files: list[File] = Field(default_factory=list) + model_config = ConfigDict(title='The sources for this build') + + archives: list[Archive] = Field( + title='Archive Sources', + json_schema_extra={ + 'description': 'Archive type sources, such as .7z, .zip, .tar.gz, etc.', + }, + ) + patches: list[Patch] = Field( + default_factory=list, + title='Patch sources', + json_schema_extra={ + 'description': 'Patches to be applied after all archives are unpacked', + }, + ) + files: list[File] = Field( + default_factory=list, + title='source file', + json_schema_extra={ + 'description': 'extra files to add the build directory', + }, + ) + + @field_validator('archives', mode='after') + @classmethod + def _validate_field(cls, value: list[Archive]) -> list[Archive]: + if not value: + raise ValueError('At least one "archive" entry is required') + return value class Quirks(BaseModel): - """The quirks section of the build toml description.""" - - force_window_gui_icon: bool = False - x_configure_prologue: str | None = Field(None) - x_renpy_archived_window_gui_icon: str | None = Field(None) + """Quirks of this build that have built-in wokarounds. + + There are a number of common issues, particularly in Ren'Py games that + flatpaker provides build steps to deal with. + """ + + model_config = ConfigDict(title='Quirks of this game to work around') + + force_window_gui_icon: bool = Field( + False, + title='Force use of img/windows_gui/icon.png', + json_schema_extra={ + 'description': ('Normally icons from a .exe or .icns if they are avilable, ' + 'however, some games do not customize these icon sources ' + 'but do provide a customized incon in the img directory. ' + 'Setting this to true forces the use of that icon.'), + }, + ) + x_configure_prologue: str | None = Field( + None, + title='Commands to run after unpacking all sources', + json_schema_extra={ + 'description': ('Extra shell commands to run after unpacking sources. ' + 'This has been replaced with the `commands` field in sources'), + 'deprecated': True, + }, + ) + x_renpy_archived_window_gui_icon: str | None = Field( + None, + title='Extract the icon from an RPA archive.', + json_schema_extra={ + 'description': ('Extract an icon from an .rpa icon. This is deprecated, and is ' + 'now an alias for force_window_gui_icon'), + 'deprecated': True, + }, + ) @field_validator('x_configure_prologue', mode='before') @classmethod @@ -159,7 +325,7 @@ def _validate_prologue(cls, v: str | None, info: ValidationInfo) -> str | None: file = info.context.get('file') assert isinstance(file, pathlib.PurePath) warnings.warn(f'{file.as_posix()}: [quirks.x_configure_prologue]: use [[sources.archives.commands]] instead', - DeprecationWarning) + DeprecationWarning) return v @@ -186,7 +352,14 @@ def _validate_only_one_icon_override(self) -> Self: class Description(BaseModel): - """The build toml description.""" + """A flatpaker build description for a Ren'Py or RPGMaker MV or MZ game""" + + model_config = ConfigDict( + title='flatpaker', + json_schema_extra={ + '$id': 'https://github.com/dcbaker/flatpaker/flatpaker.schema.json', + }, + ) common: Common appdata: AppData diff --git a/flatpaker/entry.py b/flatpaker/entry.py index f26732a..a760b25 100644 --- a/flatpaker/entry.py +++ b/flatpaker/entry.py @@ -15,6 +15,7 @@ from flatpaker.actions.build_flatpak import build_flatpak from flatpaker.actions.build_runtime import build_runtimes from flatpaker.actions.generate import generate +from flatpaker.actions.schema import dump_schema from flatpaker.actions.validate import validate if typing.TYPE_CHECKING: @@ -22,7 +23,7 @@ from flatpaker.description import EngineName class BaseArguments(typing.Protocol): - action: typing.Literal['build', 'build-runtimes', 'generate', 'validate'] + action: typing.Literal['build', 'build-runtimes', 'generate', 'validate', 'schema'] class BaseBuildArguments(BaseArguments, typing.Protocol): repo: str @@ -57,7 +58,11 @@ class GenerateArguments(BaseArguments, typing.Protocol): class ValidateArguments(typing.Protocol): descriptions: list[pathlib.Path] -@dataclasses.dataclass(slots=False, eq=False) + class SchemaArguments(typing.Protocol): + output: str + + +@dataclasses.dataclass(slots=True, eq=False) class FlatManagerConfig: remote: str @@ -65,7 +70,7 @@ class FlatManagerConfig: token: str -@dataclasses.dataclass(slots=False, eq=False) +@dataclasses.dataclass(slots=True, eq=False) class _BuildCommonConfig: """Common configuration for "build" and "build-runtimes".""" @@ -78,14 +83,14 @@ class _BuildCommonConfig: flat_manager: FlatManagerConfig | None -@dataclasses.dataclass(slots=False, eq=False) +@dataclasses.dataclass(slots=True, eq=False) class BuildFlatpakConfig(_BuildCommonConfig): """Configuration for "build".""" descriptions: list[pathlib.Path] -@dataclasses.dataclass(slots=False, eq=False) +@dataclasses.dataclass(slots=True, eq=False) class BuildRuntimeConfig(_BuildCommonConfig): """Configuration for "build-runtimes".""" @@ -93,7 +98,7 @@ class BuildRuntimeConfig(_BuildCommonConfig): update: bool -@dataclasses.dataclass(slots=False, eq=False) +@dataclasses.dataclass(slots=True, eq=False) class GenerateConfig: """Configuration for "generate".""" @@ -105,13 +110,19 @@ class GenerateConfig: files: list[str] -@dataclasses.dataclass(slots=False, eq=False) +@dataclasses.dataclass(slots=True, eq=False) class ValidateConfig: """Configuration for "validate".""" descriptions: list[pathlib.Path] +@dataclasses.dataclass(slots=True, eq=False) +class SchemaConfig: + """Configuration for "validate".""" + + output: pathlib.Path + def static_deltas(args: BuildRuntimeConfig | BuildFlatpakConfig) -> None: if not (args.deltas or args.export != 'repo'): @@ -202,10 +213,15 @@ def _parse_args() -> BaseArguments: build_parser.set_defaults(action='build') validate_parser = subparsers.add_parser( - 'validate', help='validate buidl configurations') + 'validate', help='validate build configurations') validate_parser.add_argument('descriptions', nargs='+', type=pathlib.Path, help="One or more Toml description file") validate_parser.set_defaults(action='validate') + schema_parser = subparsers.add_parser( + 'schema', help='validate build configurations') + schema_parser.add_argument('-o', '--output', action='store', default='.', help='Where to write the schema file') + schema_parser.set_defaults(action='schema') + _all_runtimes = ['renpy8', 'renpy7', 'renpy7-py3', 'rpgmaker'] runtimes_parser = subparsers.add_parser( 'build-runtimes', help='Build custom Platforms and Sdks', parents=[pp]) @@ -321,7 +337,7 @@ def _flat_manager_config(args: BaseBuildArguments) -> FlatManagerConfig | None: return FlatManagerConfig(remote, repo, token) -def _args_to_config() -> BuildFlatpakConfig | BuildRuntimeConfig | GenerateConfig | ValidateConfig: +def _args_to_config() -> BuildFlatpakConfig | BuildRuntimeConfig | GenerateConfig | ValidateConfig | SchemaConfig: args = _parse_args() match args.action: case 'build': @@ -362,6 +378,10 @@ def _args_to_config() -> BuildFlatpakConfig | BuildRuntimeConfig | GenerateConfi case 'validate': vargs = typing.cast('ValidateArguments', args) return ValidateConfig(descriptions=vargs.descriptions) + case 'schema': + scargs = typing.cast('SchemaArguments', args) + path = pathlib.Path(scargs.output) / 'flatpaker.schema.json' + return SchemaConfig(output=path.absolute()) def main() -> None: @@ -381,5 +401,7 @@ def main() -> None: success = generate(config) case ValidateConfig(): success = validate(config) + case SchemaConfig(): + success = dump_schema(config.output) sys.exit(0 if success else 1) diff --git a/pyproject.toml b/pyproject.toml index 631d3b0..d3902ec 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,7 +58,7 @@ quote-style = "preserve" [tool.bumpversion] - current_version = "0.1.1t" + current_version = "0.1.1" parse = "(?P\\d+)\\.(?P\\d+)\\.(?P\\d+)" serialize = ["{major}.{minor}.{patch}"] search = "{current_version}"