diff --git a/README.md b/README.md index 1ca1a02..ecb55fb 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ The library ships a default CLI front-end that is meant to be a convenient start - Output directory. - SVG renderer selection. - **`--only`** (short `-l`): restrict the build to the jobs whose names are given. It accepts one or more names and may be repeated. Jobs that the selected ones depend on are still built as needed, so it is a resource-saving way to rebuild or verify only a subset of an entire icon set. +- **`--format`** (short `-f`): restrict the build to the given artifact formats (`freedesktop`, `ico`, `msix`, `icns`). It accepts one or more formats and may be repeated. Sinks and sources of non-selected formats skip themselves at run time, while intermediate jobs (like temporary artifact producers) are unaffected and always run. Defaults to all formats. Because Assemblicon is a library, this CLI is not fixed. You can write your own entry point and extend (or replace) the standard argument parser with project-specific options. diff --git a/src/assemblicon/cli.py b/src/assemblicon/cli.py index 99179a9..35019a2 100644 --- a/src/assemblicon/cli.py +++ b/src/assemblicon/cli.py @@ -4,6 +4,7 @@ from pathlib import Path from argparse import ArgumentParser, Namespace from dataclasses import dataclass from .lowlevel.artrdr import SvgRenderKind +from .highlevel.address import Family class CliSvgRenderKind(enum.StrEnum): @@ -20,6 +21,26 @@ class CliSvgRenderKind(enum.StrEnum): raise ValueError(f"Unknown CLI-specific SVG render kind: {self}") +class CliArtifactFormat(enum.StrEnum): + Freedesktop = "freedesktop" + Ico = "ico" + Msix = "msix" + Icns = "icns" + + def to_assemblicon_type(self) -> Family: + match self: + case CliArtifactFormat.Freedesktop: + return Family.Freedesktop + case CliArtifactFormat.Ico: + return Family.Ico + case CliArtifactFormat.Msix: + return Family.Msix + case CliArtifactFormat.Icns: + return Family.Icns + case _: + raise ValueError(f"Unknown CLI-specific artifact format: {self}") + + @dataclass(frozen=True) class StdOpts: """Assemblicon standard command line options""" @@ -32,6 +53,11 @@ class StdOpts: """The list holding all name of jobs for building.""" svg_render: CliSvgRenderKind """The render kind for SVG file.""" + format_selection: Optional[list[CliArtifactFormat]] + """ + The list holding all formats of artifacts for building. + ``None`` for building artifacts of all formats. + """ @dataclass(frozen=True) @@ -98,6 +124,16 @@ def create_argparser(setup: StdOptsSetup) -> ArgumentParser: help="The render used for rendering SVG into PNG.", metavar="RENDER", ) + parser.add_argument( + "-f", + "--format", + dest="format_selection", + action="extend", + nargs="+", + type=CliArtifactFormat, + help="Only build artifacts of given formats. Repeatable. Default all formats.", + metavar="FORMAT", + ) return parser @@ -107,4 +143,5 @@ def parse_standard_opts(cfg: StdOptsSetup, ns: Namespace) -> StdOpts: output_directory=ns.output if cfg.require_output else None, selection=ns.selection, svg_render=ns.svg_render, + format_selection=ns.format_selection, ) diff --git a/src/assemblicon/highlevel/address.py b/src/assemblicon/highlevel/address.py index 079e07b..9fc0efc 100644 --- a/src/assemblicon/highlevel/address.py +++ b/src/assemblicon/highlevel/address.py @@ -1,6 +1,19 @@ import enum +class Family(enum.StrEnum): + """The family of an output artifact, used by runtime family filtering.""" + + Freedesktop = "freedesktop" + """FreeDesktop compatible PNG icons.""" + Ico = "ico" + """Windows ICO icons.""" + Msix = "msix" + """Windows MSIX package icons.""" + Icns = "icns" + """macOS ICNS icons.""" + + class AddressDomain(enum.IntEnum): Input = enum.auto() Temporary = enum.auto() diff --git a/src/assemblicon/highlevel/environment.py b/src/assemblicon/highlevel/environment.py index 7d30287..f9d1c85 100644 --- a/src/assemblicon/highlevel/environment.py +++ b/src/assemblicon/highlevel/environment.py @@ -7,6 +7,7 @@ from pathlib import Path from typing import TypeVar, Generic, Callable from ..lowlevel.environment import Prologue as LowPrologue, Environment as LowEnv from ..lowlevel.artrdr import SvgRenderKind +from .address import Family from .utils import ( FdContext, IcoCategory, @@ -53,6 +54,16 @@ class Prologue(LowPrologue): The only argument in function is the path to user temporary directory. ``None`` for no extra initializer. """ + allowed_families: set[Family] | None = None + """ + The set of artifact families allowed to be generated. + Output-family sinks and sources check this at run time and silently + skip themselves when their family is not listed, which provides a + resource-saving way to only produce a subset of output families. + Intermediate artifacts (like temporary ones) have no family and are + always produced. ``None`` for no restriction, and an empty set means + no output family is allowed. + """ def __post_init__(self) -> None: super().__post_init__() @@ -70,6 +81,15 @@ class Prologue(LowPrologue): for targetsize in self.msix_ext_targetsizes: check_msix_targetsize(targetsize) + def allow_family(self, family: Family) -> bool: + """ + Check whether given artifact family is allowed to be generated. + + :param family: The family to be checked. + :returns: ``True`` if this family is allowed to be generated. + """ + return self.allowed_families is None or family in self.allowed_families + _TEMP_USER_DIR: str = "user" _TEMP_LIB_DIR: str = "assemblicon" diff --git a/src/assemblicon/highlevel/sink/fdart_sink.py b/src/assemblicon/highlevel/sink/fdart_sink.py index 43e33d5..1f0ff7e 100644 --- a/src/assemblicon/highlevel/sink/fdart_sink.py +++ b/src/assemblicon/highlevel/sink/fdart_sink.py @@ -1,6 +1,6 @@ from dataclasses import dataclass from .common import Sink -from ..address import Address, AddressDomain +from ..address import Address, AddressDomain, Family from ..environment import Environment from ..utils import Artifact, FdContext, check_vart from ...lowlevel import artio, artrdr @@ -26,6 +26,9 @@ class FdArtSink(Sink): return Address(AddressDomain.PosixFdOutput, str(self.__kind), self.__name) def push(self, env: Environment, art: Artifact) -> None: + if not env.prologue.allow_family(Family.Freedesktop): + return + LOGGER.info( 'Saving FreeDesktop artifact with kind "%s" and name "%s".', self.__kind, diff --git a/src/assemblicon/highlevel/sink/icnsart_sink.py b/src/assemblicon/highlevel/sink/icnsart_sink.py index fa7aec3..9aea18f 100644 --- a/src/assemblicon/highlevel/sink/icnsart_sink.py +++ b/src/assemblicon/highlevel/sink/icnsart_sink.py @@ -1,6 +1,6 @@ from dataclasses import dataclass from .common import Sink -from ..address import Address, AddressDomain +from ..address import Address, AddressDomain, Family from ..environment import Environment from ..utils import Artifact, IcnsCategory, check_vart from ...lowlevel import artrdr @@ -26,6 +26,9 @@ class IcnsArtSink(Sink): return Address(AddressDomain.MacIcnsOutput, str(self.__kind), self.__name) def push(self, env: Environment, art: Artifact) -> None: + if not env.prologue.allow_family(Family.Icns): + return + LOGGER.info( 'Saving macOS ICNS artifact with kind "%s" and name "%s".', self.__kind, diff --git a/src/assemblicon/highlevel/sink/icoart_sink.py b/src/assemblicon/highlevel/sink/icoart_sink.py index 32e8c98..22a9c02 100644 --- a/src/assemblicon/highlevel/sink/icoart_sink.py +++ b/src/assemblicon/highlevel/sink/icoart_sink.py @@ -1,6 +1,6 @@ from dataclasses import dataclass from .common import Sink -from ..address import Address, AddressDomain +from ..address import Address, AddressDomain, Family from ..environment import Environment from ..utils import Artifact, IcoCategory, check_vart from ...lowlevel import artrdr @@ -26,6 +26,9 @@ class IcoArtSink(Sink): return Address(AddressDomain.WinIcoOutput, str(self.__kind), self.__name) def push(self, env: Environment, art: Artifact) -> None: + if not env.prologue.allow_family(Family.Ico): + return + LOGGER.info( 'Saving Windows ICO artifact with kind "%s" and name "%s".', self.__kind, diff --git a/src/assemblicon/highlevel/sink/msixart_sink.py b/src/assemblicon/highlevel/sink/msixart_sink.py index f4c132c..5887135 100644 --- a/src/assemblicon/highlevel/sink/msixart_sink.py +++ b/src/assemblicon/highlevel/sink/msixart_sink.py @@ -1,6 +1,6 @@ from dataclasses import dataclass from .common import Sink -from ..address import Address, AddressDomain +from ..address import Address, AddressDomain, Family from ..environment import Environment from ..utils import ( Artifact, @@ -33,6 +33,9 @@ class MsixArtSink(Sink): return Address(AddressDomain.WinMsixOutput, str(self.__kind), self.__name) def push(self, env: Environment, art: Artifact) -> None: + if not env.prologue.allow_family(Family.Msix): + return + LOGGER.info( 'Saving Windows MSIX artifact with kind "%s" and name "%s".', self.__kind, diff --git a/src/assemblicon/highlevel/source/fdart_source.py b/src/assemblicon/highlevel/source/fdart_source.py index c69e0dc..35d0633 100644 --- a/src/assemblicon/highlevel/source/fdart_source.py +++ b/src/assemblicon/highlevel/source/fdart_source.py @@ -1,5 +1,5 @@ from .common import Source -from ..address import Address, AddressDomain +from ..address import Address, AddressDomain, Family from ..environment import Environment from ..sink.fdart_sink import FdArtSink from ..utils import FdContext @@ -20,6 +20,9 @@ class FdArtSource(Source): return Address(AddressDomain.PosixFdOutput, str(self.__kind), self.__name) def link_to(self, env: Environment, fd_sink: FdArtSink) -> None: + if not env.prologue.allow_family(Family.Freedesktop): + return + sink_hint = fd_sink.push_hint() LOGGER.info( 'Duplicating FreeDesktop artifact from kind "%s" name "%s" to kind "%s" name "%s".', diff --git a/src/assemblicon/highlevel/source/icnsart_source.py b/src/assemblicon/highlevel/source/icnsart_source.py index 17878a6..5c59e89 100644 --- a/src/assemblicon/highlevel/source/icnsart_source.py +++ b/src/assemblicon/highlevel/source/icnsart_source.py @@ -1,5 +1,5 @@ from .common import Source -from ..address import Address, AddressDomain +from ..address import Address, AddressDomain, Family from ..environment import Environment from ..sink.icnsart_sink import IcnsArtSink from ..utils import IcnsCategory @@ -20,6 +20,9 @@ class IcnsArtSource(Source): return Address(AddressDomain.MacIcnsOutput, str(self.__kind), self.__name) def link_to(self, env: Environment, icns_sink: IcnsArtSink) -> None: + if not env.prologue.allow_family(Family.Icns): + return + sink_hint = icns_sink.push_hint() LOGGER.info( 'Duplicating macOS ICNS artifact from kind "%s" name "%s" to kind "%s" name "%s".', diff --git a/src/assemblicon/highlevel/source/icoart_source.py b/src/assemblicon/highlevel/source/icoart_source.py index 3a17268..a004a2c 100644 --- a/src/assemblicon/highlevel/source/icoart_source.py +++ b/src/assemblicon/highlevel/source/icoart_source.py @@ -1,5 +1,5 @@ from .common import Source -from ..address import Address, AddressDomain +from ..address import Address, AddressDomain, Family from ..environment import Environment from ..sink.icoart_sink import IcoArtSink from ..utils import IcoCategory @@ -20,6 +20,9 @@ class IcoArtSource(Source): return Address(AddressDomain.WinIcoOutput, str(self.__kind), self.__name) def link_to(self, env: Environment, ico_sink: IcoArtSink) -> None: + if not env.prologue.allow_family(Family.Ico): + return + sink_hint = ico_sink.push_hint() LOGGER.info( 'Duplicating Windows ICO artifact from kind "%s" name "%s" to kind "%s" name "%s".', diff --git a/src/assemblicon/highlevel/source/msixart_source.py b/src/assemblicon/highlevel/source/msixart_source.py index 9521af4..7d4d6fa 100644 --- a/src/assemblicon/highlevel/source/msixart_source.py +++ b/src/assemblicon/highlevel/source/msixart_source.py @@ -1,5 +1,5 @@ from .common import Source -from ..address import Address, AddressDomain +from ..address import Address, AddressDomain, Family from ..environment import Environment from ..sink.msixart_sink import MsixArtSink, MsixArtSinkHint from ..utils import MsixCategory, MsixAppLogoKind, MsixAppPlatedKind @@ -20,6 +20,9 @@ class MsixArtSource(Source): return Address(AddressDomain.WinMsixOutput, str(self.__kind), self.__name) def link_to(self, env: Environment, msix_sink: MsixArtSink) -> None: + if not env.prologue.allow_family(Family.Msix): + return + sink_hint = msix_sink.push_hint() LOGGER.info( 'Duplicating Windows MSIX artifact from kind "%s" name "%s" to kind "%s" name "%s".',