From 518f3c993046fef9f9c35cb7e8fa7d23757ff949 Mon Sep 17 00:00:00 2001 From: Valentin Berlier Date: Mon, 9 Oct 2023 20:19:34 +0200 Subject: [PATCH] feat: support overlays --- README.md | 18 +++++ lectern/extract.py | 37 +++++++++- lectern/serialize.py | 68 +++++++++++++++++-- .../examples__markdown_README_md__0.pack.md | 25 +++++++ .../README.md | 25 +++++++ .../examples__text_README_md__0.pack.txt | 18 +++++ 6 files changed, 182 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index a1c93b8..72f73f0 100644 --- a/README.md +++ b/README.md @@ -378,6 +378,24 @@ Finally, there's a `strip_final_newline` modifier that removes the final newline say This function doesn't have a final newline. ``` +## Overlays + +You can use the `@overlay` directive to make the following directives apply to a specific pack overlay (introduced in [Java Edition 1.20.2](https://minecraft.wiki/w/Java_Edition_1.20.2)). + +`@overlay({"min_inclusive": 16, "max_inclusive": 17}) dummy_overlay` + +You can specify the `formats` supported by this overlay as a modifier. From now, on all the directives will apply to the overlay `dummy_overlay`. + +`@function tutorial:greeting` + +```mcfunction +say Hello from overlay! +``` + +You can switch to another overlay at any time by using the `@overlay` directive again. To go back to the main pack, use the `@endoverlay` directive. + +`@endoverlay` + ## Command-line utility ```bash diff --git a/lectern/extract.py b/lectern/extract.py index d713168..bf4e6e7 100644 --- a/lectern/extract.py +++ b/lectern/extract.py @@ -8,6 +8,7 @@ ] +import json import re from dataclasses import replace from itertools import islice @@ -21,6 +22,7 @@ List, Mapping, Optional, + Set, Tuple, Union, ) @@ -54,16 +56,22 @@ def __init__(self, cache: Optional[Cache] = None): self.escaped_regex = None self.cache = cache + def generate_directives(self) -> str: + names = list(self.directives) + names.append("overlay") + names.append("endoverlay") + return "|".join(names) + def generate_regex(self) -> str: """Return a regex that can match the current directives.""" - names = "|".join(self.directives) + names = self.generate_directives() modifier = r"(?:\((?P[^)]*)\)|\b)" arguments = r"(?P.*)" return f"@(?P{names}){modifier}{arguments}" def generate_escaped_regex(self) -> str: """Return a regex that can match escaped fragments.""" - names = "|".join(self.directives) + names = self.generate_directives() return rf"(@@+(?:{names})\b.*)" def compile_regex(self, regex: str) -> "re.Pattern[str]": @@ -121,13 +129,36 @@ def apply_directives( loaders: Iterable[FragmentLoader] = (), ): """Apply directives into a blank data pack and a blank resource pack.""" + original_packs = assets, data + added_overlays: Set[str] = set() + for fragment in fragments: for loader in loaders: fragment = loader(fragment, directives) if not fragment: break if fragment: - directives[fragment.directive](fragment, assets, data) + if fragment.directive == "overlay": + directory = fragment.expect("directory") + formats: Any = ( + json.loads(fragment.modifier) if fragment.modifier else None + ) + assets = assets.overlays.setdefault( + directory, supported_formats=formats + ) + data = data.overlays.setdefault( + directory, supported_formats=formats + ) + added_overlays.add(directory) + elif fragment.directive == "endoverlay": + assets, data = original_packs + else: + directives[fragment.directive](fragment, assets, data) + + for pack in original_packs: + for directory in added_overlays: + if not pack.overlays[directory]: + del pack.overlays[directory] def parse_fragments( self, diff --git a/lectern/serialize.py b/lectern/serialize.py index 214b3bf..44c24b7 100644 --- a/lectern/serialize.py +++ b/lectern/serialize.py @@ -22,6 +22,7 @@ Dict, Iterable, Iterator, + List, Mapping, Optional, Type, @@ -29,7 +30,7 @@ ) from urllib.parse import urlparse -from beet import DataPack, File, NamespaceFile, ResourcePack, TextFileBase +from beet import DataPack, File, NamespaceFile, ResourcePack, TextFile, TextFileBase from beet.core.utils import normalize_string EXTENSION_HIGHLIGHTING = { @@ -166,6 +167,36 @@ def serialize( mapping: Mapping[Type[NamespaceFile], str], ) -> str: """Return the serialized representation.""" + result: List[str] = [] + + for pack, extra_directive in [ + (assets, "resource_pack"), + (data, "data_pack"), + ]: + if not self.pack_filter(pack): + continue + + result.append(self.serialize_pack(pack, extra_directive, mapping)) + + if pack.overlay_parent is None: + should_end = False + for overlay in pack.overlays.values(): + if overlay: + should_end = True + result.append( + self.serialize_pack(overlay, extra_directive, mapping) + ) + if should_end: + result.append("@endoverlay\n") + + return "\n".join(result) + + def serialize_pack( + self, + pack: Union[DataPack, ResourcePack], + extra_directive: str, + mapping: Mapping[Type[NamespaceFile], str], + ) -> str: escaped_regex = self.get_escaped_regex(mapping) return "\n".join( @@ -176,15 +207,19 @@ def serialize( + b64encode(content).decode() + "\n" ) - for pack, extra_directive in [ - (assets, "resource_pack"), - (data, "data_pack"), - ] - if self.pack_filter(pack) for directive_name, argument, file_instance in chain( + ( + [("overlay", pack.overlay_name, TextFile())] + if pack.overlay_name is not None + else [] + ), ( (extra_directive, path, file_instance) for path, file_instance in pack.extra.items() + if not ( + pack.overlay_parent is not None + and path in ("pack.mcmeta", "pack.png") + ) ), ( ( @@ -286,7 +321,12 @@ def serialize_pack( """Yield markdown chunks for the given pack.""" yield f"## {title}" + if pack.overlay_name is not None: + yield f"\n`@overlay {pack.overlay_name}`" + for path, file_instance in pack.extra.items(): + if pack.overlay_parent is not None and path in ("pack.mcmeta", "pack.png"): + continue yield from self.format_serialized_file( self.serialize_file_instance( pack_directive, @@ -338,6 +378,22 @@ def serialize_pack( ) yield "" + if pack.overlay_parent is None: + should_end = False + for directory, overlay in pack.overlays.items(): + if overlay: + should_end = True + yield from self.serialize_pack( + f"Overlay `{directory}`", + pack_directive, + overlay, + mapping, + external_files, + external_prefix, + ) + if should_end: + yield "`@endoverlay`\n" + def format_serialized_file(self, chunks: Iterable[str]) -> Iterator[str]: """Format the markdown chunks for serializing file instances.""" if self.flat: diff --git a/tests/snapshots/examples__markdown_README_md__0.pack.md b/tests/snapshots/examples__markdown_README_md__0.pack.md index c8fab5f..3ba0614 100644 --- a/tests/snapshots/examples__markdown_README_md__0.pack.md +++ b/tests/snapshots/examples__markdown_README_md__0.pack.md @@ -9,6 +9,17 @@ "pack": { "pack_format": 18, "description": "" + }, + "overlays": { + "entries": [ + { + "formats": { + "min_inclusive": 16, + "max_inclusive": 17 + }, + "directory": "dummy_overlay" + } + ] } } ``` @@ -203,3 +214,17 @@ say foo ```mcfunction say foo ``` + +## Overlay `dummy_overlay` + +`@overlay dummy_overlay` + +### tutorial + +`@function tutorial:greeting` + +```mcfunction +say Hello from overlay! +``` + +`@endoverlay` diff --git a/tests/snapshots/examples__markdown_external_files_README_md__0.pack.md_external_files/README.md b/tests/snapshots/examples__markdown_external_files_README_md__0.pack.md_external_files/README.md index decb9e5..f43e87f 100644 --- a/tests/snapshots/examples__markdown_external_files_README_md__0.pack.md_external_files/README.md +++ b/tests/snapshots/examples__markdown_external_files_README_md__0.pack.md_external_files/README.md @@ -9,6 +9,17 @@ "pack": { "pack_format": 18, "description": "" + }, + "overlays": { + "entries": [ + { + "formats": { + "min_inclusive": 16, + "max_inclusive": 17 + }, + "directory": "dummy_overlay" + } + ] } } ``` @@ -203,3 +214,17 @@ say foo ```mcfunction say foo ``` + +## Overlay `dummy_overlay` + +`@overlay dummy_overlay` + +### tutorial + +`@function tutorial:greeting` + +```mcfunction +say Hello from overlay! +``` + +`@endoverlay` diff --git a/tests/snapshots/examples__text_README_md__0.pack.txt b/tests/snapshots/examples__text_README_md__0.pack.txt index fe0e8f8..1eea09e 100644 --- a/tests/snapshots/examples__text_README_md__0.pack.txt +++ b/tests/snapshots/examples__text_README_md__0.pack.txt @@ -3,6 +3,17 @@ "pack": { "pack_format": 18, "description": "" + }, + "overlays": { + "entries": [ + { + "formats": { + "min_inclusive": 16, + "max_inclusive": 17 + }, + "directory": "dummy_overlay" + } + ] } } @@ -151,3 +162,10 @@ say foo @function text_in_block:foo say foo + +@overlay dummy_overlay + +@function tutorial:greeting +say Hello from overlay! + +@endoverlay