diff --git a/automate/resources/blobfox.yml b/automate/resources/blobfox.yml index 7e792be..df20626 100644 --- a/automate/resources/blobfox.yml +++ b/automate/resources/blobfox.yml @@ -1,12 +1,10 @@ name: 'blobfox' -basedOff: - - null # all variants from 'blob' will be imported in this unit, unless they are already present +basedOff: null variants: - name: 'base' src: '../vector/blobfox.svg' - name: 'boop' - base: 'base' objects: - type: svg src: 'resources/lgbtq_heart.svg' diff --git a/automate/resources/neugeme.yml b/automate/resources/neugeme.yml new file mode 100644 index 0000000..8169fd0 --- /dev/null +++ b/automate/resources/neugeme.yml @@ -0,0 +1,11 @@ +name: 'neugeme' +basedOff: 'blobfox' # all variants from 'blobfox' will be imported in this unit, unless they are already present +variants: + - name: 'base' + overwrites: + - id: 'body' + color: 0x02FFFD + - id: 'hair' + color: 0x02FFFD + - id: 'blobfox' + remove: true diff --git a/automate/src/data/emote.py b/automate/src/data/emote.py index e4ce88c..e90c485 100644 --- a/automate/src/data/emote.py +++ b/automate/src/data/emote.py @@ -6,10 +6,10 @@ from data.object import Object from data.overwrite import Overwrite -@dataclass(frozen=True) +@dataclass class Emote: name: str - origin: str + origin: list[ str ] #: the origins in order of definition base: str | None = None src: Path | None = None objects: list[Object] = None @@ -19,7 +19,7 @@ class Emote: def load( cls, data: list[dict], origin: str ) -> list[Self]: return [ Emote( - origin=origin, + origin=[ origin ], **entry | { 'overwrites': Overwrite.load( entry.get( 'overwrites', [ ] ) ), 'objects': Object.load( entry.get( 'objects', [ ] ) ) diff --git a/automate/src/data/overwrite.py b/automate/src/data/overwrite.py index 00093d7..dcb68d4 100644 --- a/automate/src/data/overwrite.py +++ b/automate/src/data/overwrite.py @@ -5,7 +5,7 @@ from typing_extensions import Self @dataclass(frozen=True) class Overwrite: id: str - color: str | None + color: str | None = None remove: bool = False @classmethod diff --git a/automate/src/data/variantList.py b/automate/src/data/variantList.py deleted file mode 100644 index b978e5b..0000000 --- a/automate/src/data/variantList.py +++ /dev/null @@ -1,52 +0,0 @@ -from dataclasses import dataclass -from typing import Final -from collections.abc import Collection, Iterator - -from data.emote import Emote - - -# noinspection PyFinal -@dataclass(frozen=True, slots=True) -class VariantList(Collection[Emote]): - """ An immutable variant list """ - name: Final[ str ] - basedOff: Final[ list[ str ] ] - variants: Final[ tuple[Emote] ] - - def __init__( self, name: str, variants: list[ dict ], basedOff: list[str] | None = None ) -> None: - object.__setattr__( - self, - 'variants', - tuple( Emote.load( variants, name ) ) - ) - object.__setattr__( self, 'basesOff', basedOff or [] ) - object.__setattr__( self, 'name', name ) - - def __iter__( self ) -> Iterator[Emote ]: - return self.variants.__iter__() - - def __contains__( self, item: object ) -> bool: - if isinstance( item, str ): - for elem in self.variants: - if elem.name == item: - return True - return False - return item in self.variants - - def __getitem__( self, item: int | str ) -> Emote: - if isinstance( item, int ): - return self.variants[ item ] - - if isinstance( item, str ): - for elem in self.variants: - if elem.name == item: - return elem - raise KeyError( f'A variant with name "{item}" does not exist' ) - - raise ValueError( f'Invalid __getitem__ input: {item}' ) - - def __len__( self ) -> int: - return len( self.variants ) - - def __repr__( self ) -> str: - return f'VariantList{{name={self.name}, variants={repr(self.variants)}}}' diff --git a/automate/src/data/variants.py b/automate/src/data/variants.py new file mode 100644 index 0000000..e038b8a --- /dev/null +++ b/automate/src/data/variants.py @@ -0,0 +1,80 @@ +from __future__ import annotations + +import typing +from dataclasses import dataclass +from pathlib import Path +from typing import Final +from collections.abc import Collection, Iterator + +import util +from data.emote import Emote + + +if typing.TYPE_CHECKING: + from generator import Generator + + +# noinspection PyFinal +@dataclass(frozen=True, slots=True) +class Variants( Collection[Emote] ): + """ An immutable variant list """ + name: Final[ str ] + basedOff: Final[ tuple[ str ] ] + variants: Final[ tuple[ Emote ] ] + + def __init__( self, path: Path, gen: Generator ) -> None: + data = util.load( path ) + + object.__setattr__( self, 'name', data[ 'name' ] ) + object.__setattr__( self, 'basedOff', tuple( data[ 'basedOff' ] or [ ] ) ) + + if data['basedOff']: + # loading a set based off another, load the base one before this + base = gen.load( path.parent / f'{data["basedOff"]}.yml' ) + + # additional emotes may be defined + additional = [ ] + # apply overwrites and append new emotes to `additional` + for emote in Emote.load( data['variants'], self.name ): + if emote.name in base: + base._applyOverwrite( emote ) + else: + additional += [ emote ] + # save the newly created set of emotes + object.__setattr__( self, 'variants', base.variants + tuple( additional ) ) + else: + # loading a baseless set, just load it directly + object.__setattr__( self, 'variants', tuple( Emote.load( data['variants'], self.name ) ) ) + + def __iter__( self ) -> Iterator[Emote ]: + return self.variants.__iter__() + + def __contains__( self, item: object ) -> bool: + if isinstance( item, str ): + for elem in self.variants: + if elem.name == item: + return True + return False + return item in self.variants + + def __getitem__( self, item: int | str ) -> Emote: + if isinstance( item, int ): + return self.variants[ item ] + + if isinstance( item, str ): + for elem in self.variants: + if elem.name == item: + return elem + raise KeyError( f'A variant with name "{item}" does not exist' ) + + raise ValueError( f'Invalid __getitem__ input: {item}' ) + + def __len__( self ) -> int: + return len( self.variants ) + + def _applyOverwrite( self, overwriter: Emote ) -> None: + emote = self[ overwriter.name ] + emote.origin += overwriter.origin + emote.overwrites += overwriter.overwrites + emote.objects += overwriter.objects + diff --git a/automate/src/generator.py b/automate/src/generator.py index 0fc70ec..67d686c 100644 --- a/automate/src/generator.py +++ b/automate/src/generator.py @@ -4,25 +4,21 @@ import yaml from cairosvg.surface import Surface -from data.variantList import VariantList +from data.variants import Variants class Generator: - declarations: dict[ str, VariantList ] + declarations: dict[ str, Variants ] surfaces: dict[ str, Surface ] def __init__( self, declFile: Path ) -> None: if not declFile.exists(): raise FileNotFoundError('Declaration file does not exist!') - self.recursiveLoad( declFile ) + self.declarations = { declFile.name[:-4]: self.load( declFile ) } - def recursiveLoad( self, declFile: Path ) -> None: - variants = VariantList( **yaml.load( declFile.read_text(), yaml.FullLoader ) ) - self.declarations[ variants.name ] = variants - - if variants.basedOff is not None: - self.recursiveLoad( declFile.parent / f'{variants.basedOff}.yml' ) + def load( self, declFile: Path ) -> Variants: + return Variants( declFile, self ) def generate( self, outputDir: Path = Path('.') ) -> None: """ @@ -33,9 +29,8 @@ class Generator: if not outputDir.exists(): outputDir.mkdir() - for variant in self.variants: - pass + print( self.declarations['neugeme'] ) if __name__ == '__main__': - Generator( Path('./resources/blobfox.yml') ).generate( Path('./run') ) + Generator( Path('./resources/neugeme.yml') ).generate( Path('./run') ) diff --git a/automate/src/util.py b/automate/src/util.py new file mode 100644 index 0000000..dbca3eb --- /dev/null +++ b/automate/src/util.py @@ -0,0 +1,14 @@ +from pathlib import Path +from typing import Any + +import yaml + + +def load( path: Path ) -> Any: + """ Parse the first YAML document in a stream and produce the corresponding Python object. """ + return yaml.load( path.read_text(), yaml.FullLoader ) + + +def loads( data: str ) -> Any: + """ Parse the first YAML document in a string and produce the corresponding Python object. """ + return yaml.load( data, yaml.FullLoader )