The great refactoring (move to hexdoc package and add reexports)

This commit is contained in:
object-Object 2023-07-24 20:40:28 -04:00
parent 2960568e91
commit 03e7683ae1
53 changed files with 475 additions and 404 deletions

36
.vscode/settings.json vendored
View file

@ -1,36 +0,0 @@
{
"[python]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true,
},
"editor.rulers": [88],
},
"[html][jinja-html]": {
"editor.rulers": [120],
},
"python.formatting.provider": "black",
"python.analysis.typeCheckingMode": "strict", // god save us
"python.analysis.diagnosticSeverityOverrides": {
"reportMissingParameterType": "error",
"reportUnknownParameterType": "error",
"reportUnknownArgumentType": "warning",
"reportUnknownLambdaType": "warning",
"reportUnknownVariableType": "none",
"reportUnknownMemberType": "warning",
"reportUnnecessaryComparison": "warning",
"reportMissingTypeArgument": "warning",
"reportUnusedImport": "information",
"reportPrivateUsage": "warning",
"reportUnnecessaryIsInstance": "information",
},
"python.analysis.diagnosticMode": "workspace",
"python.languageServer": "Pylance",
"python.testing.cwd": "${workspaceFolder}/doc",
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
"python.analysis.extraPaths": ["${workspaceFolder}/doc/src", "${workspaceFolder}/doc/test"],
"isort.args": [
"--settings", "doc/pyproject.toml"
]
}

11
doc/.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,11 @@
{
// List of extensions which should be recommended for users of this workspace.
"recommendations": [
"ms-python.vscode-pylance",
"ms-python.isort",
],
// List of extensions recommended by VS Code that should not be recommended for users of this workspace.
"unwantedRecommendations": [
"ms-python.black-formatter",
]
}

View file

@ -17,7 +17,7 @@
"type": "python",
"request": "launch",
"cwd": "${workspaceFolder}/doc",
"module": "hexcasting.scripts.main",
"module": "hexdoc",
"args": [
"properties.toml",
],

18
doc/.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,18 @@
{
"[python]": {
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": true,
},
"editor.rulers": [88],
},
"python.formatting.provider": "black",
"isort.importStrategy": "fromEnvironment",
"python.languageServer": "Pylance",
"python.analysis.diagnosticMode": "workspace",
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true,
"[html][jinja-html]": {
"editor.rulers": [120],
},
}

View file

@ -51,12 +51,8 @@ package = "{src}/main/java/at/petrak/hexcasting"
resources = "{src}/main/resources"
generated = "{src}/generated/resources"
pattern_stubs = [
# these are tables so we have the option to add extra per-stub configs (eg. regex)
# NOTE: each ^ is like ../ in a file path (^key and ^.key are both valid)
# the parent of an item in an array is the table containing the array, not the array
# so in this case, {^.package} is common.package
{ file = "{^.package}/common/casting/RegisterPatterns.java" },
{ file = "{^.package}/interop/pehkui/PehkuiInterop.java" },
"{package}/common/casting/RegisterPatterns.java",
"{package}/interop/pehkui/PehkuiInterop.java",
]
[fabric]
@ -65,7 +61,7 @@ package = "{src}/main/java/at/petrak/hexcasting/fabric"
resources = "{src}/main/resources"
generated = "{src}/generated/resources"
pattern_stubs = [
{ file = "{^.package}/interop/gravity/GravityApiInterop.java" },
"{package}/interop/gravity/GravityApiInterop.java",
]
[forge]

View file

@ -1,11 +1,10 @@
# project metadata
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "HexDoc" # TODO: i'm pretty sure i had funnier ideas than this
name = "HexDoc"
version = "0.1.0"
authors = [
{ name="Alwinfy" },
@ -16,45 +15,108 @@ requires-python = ">=3.11"
dependencies = [
"typing_extensions~=4.7.0",
"typed-argument-parser~=1.8.0",
"pydantic==2.0",
"pydantic~=2.0",
"Jinja2~=3.1.2",
]
[project.optional-dependencies]
dev = [
"black==23.7.0",
"isort==5.12.0",
"pytest==7.3.1",
"syrupy==4.0.2",
]
[project.scripts]
hexdoc = "hexdoc.scripts.hexdoc:main"
[project.entry-points."hexdoc.Page"]
hexdoc-patchouli = "patchouli.page.pages"
hexdoc-hexcasting = "hexcasting.hex_pages"
hexdoc-abstract-hexcasting = "hexcasting.abstract_hex_pages"
hexdoc-patchouli = "hexdoc.patchouli.page.pages"
hexdoc-hexcasting = "hexdoc.hexcasting.page.hex_pages"
[project.entry-points."hexdoc.Recipe"]
hexdoc-minecraft = "minecraft.recipe.recipes"
hexdoc-hexcasting = "hexcasting.hex_recipes"
hexdoc-minecraft = "hexdoc.minecraft.recipe.recipes"
hexdoc-hexcasting = "hexdoc.hexcasting.hex_recipes"
[project.entry-points."hexdoc.ItemIngredient"]
hexdoc-minecraft = "minecraft.recipe.ingredients"
hexdoc-hexcasting = "hexcasting.hex_recipes"
hexdoc-minecraft = "hexdoc.minecraft.recipe.ingredients"
hexdoc-hexcasting = "hexdoc.hexcasting.hex_recipes"
# Hatch settings (the build backend)
[tool.hatch.metadata]
allow-direct-references = true # TODO: remove when we switch to Pydantic
[tool.hatch.build]
packages = ["src/common", "src/hexcasting", "src/minecraft", "src/patchouli"]
packages = ["src/hexdoc"]
# tests, formatting, and (TODO:) type checking
[tool.pytest.ini_options]
addopts = [
"--import-mode=importlib"
]
addopts = ["--import-mode=importlib"]
markers = [
"filename: name of file for fixtures to create",
"file_contents: data for fixtures to write to files",
"fixture_data: other misc data",
]
[tool.coverage.report]
include_namespace_packages = true
[tool.isort]
profile = "black"
combine_as_imports = true
[tool.pyright]
pythonVersion = "3.11"
pythonPlatform = "All"
# mostly we use strict mode
# but pyright doesn't allow decreasing error severity in strict mode
# so we need to manually specify all of the strict mode overrides so we can do that :/
typeCheckingMode = "basic"
strictDictionaryInference = true
strictListInference = true
strictSetInference = true
reportAssertAlwaysTrue = "error"
reportConstantRedefinition = "error"
reportDeprecated = "error"
reportDuplicateImport = "error"
reportFunctionMemberAccess = "error"
reportIncompatibleMethodOverride = "error"
reportIncompatibleVariableOverride = "error"
reportIncompleteStub = "error"
reportInconsistentConstructor = "error"
reportInvalidStringEscapeSequence = "error"
reportInvalidStubStatement = "error"
reportInvalidTypeVarUse = "error"
reportMatchNotExhaustive = "error"
reportMissingParameterType = "error"
reportMissingTypeStubs = "error"
reportOverlappingOverload = "error"
reportSelfClsParameterName = "error"
reportTypeCommentUsage = "error"
reportUnknownParameterType = "error"
reportUnnecessaryCast = "error"
reportUnnecessaryContains = "error"
reportUnsupportedDunderAll = "error"
reportUntypedBaseClass = "error"
reportUntypedClassDecorator = "error"
reportUntypedFunctionDecorator = "error"
reportUntypedNamedTuple = "error"
reportUnusedClass = "error"
reportUnusedExpression = "error"
reportUnusedFunction = "error"
reportUnusedVariable = "error"
reportWildcardImportFromLibrary = "error"
reportMissingTypeArgument = "warning"
reportPrivateUsage = "warning"
reportUnknownArgumentType = "warning"
reportUnknownLambdaType = "warning"
reportUnknownMemberType = "warning"
reportUnnecessaryComparison = "warning"
reportUnnecessaryIsInstance = "warning"
reportUnusedImport = "warning"
reportUnknownVariableType = "none"

View file

@ -1,6 +0,0 @@
-e . # install package locally as editable
black==22.10.0 # formatting
isort==5.12.0 # formatting 2
pytest==7.3.1 # testing framework
syrupy==4.0.2 # snapshot tests
beautifulsoup4==4.12.2 # html pretty print so the snapshot diffs are actually usable

View file

@ -1,9 +0,0 @@
__all__ = [
"HexBook",
"HexContext",
"AnyHexContext",
"HexBookModel",
]
from .hex_book import AnyHexContext, HexBook, HexBookModel, HexContext

View file

@ -1,68 +0,0 @@
import re
from enum import Enum
from pathlib import Path
from typing import Annotated, Any, Generator
from pydantic import BeforeValidator
from common.model import HexDocModel
from minecraft.resource import ResourceLocation
class Direction(Enum):
NORTH_EAST = 0
EAST = 1
SOUTH_EAST = 2
SOUTH_WEST = 3
WEST = 4
NORTH_WEST = 5
@classmethod
def validate(cls, value: str | int | Any):
match value:
case str():
return cls[value]
case int():
return cls(value)
case _:
return value
DirectionField = Annotated[Direction, BeforeValidator(Direction.validate)]
class RawPatternInfo(HexDocModel[Any]):
startdir: DirectionField
signature: str
is_per_world: bool = False
q: int | None = None
r: int | None = None
class PatternInfo(RawPatternInfo):
id: ResourceLocation
@property
def name(self):
return self.id.path
class PatternStubFile(HexDocModel[Any]):
file: Path
def load_patterns(
self,
modid: str,
pattern_re: re.Pattern[str],
) -> Generator[PatternInfo, None, None]:
# TODO: add Gradle task to generate json with this data. this is dumb and fragile.
pattern_data = self.file.read_text("utf-8")
for match in pattern_re.finditer(pattern_data):
signature, startdir, name, is_per_world = match.groups()
yield PatternInfo(
startdir=Direction[startdir],
signature=signature,
is_per_world=bool(is_per_world),
id=ResourceLocation(modid, name),
)

View file

@ -0,0 +1,13 @@
__all__ = [
"AnyHexContext",
"HexBook",
"HexBookType",
"HexContext",
"Direction",
"PatternInfo",
"RawPatternInfo",
]
from .hex_book import AnyHexContext, HexBook, HexBookType, HexContext
from .pattern import Direction, PatternInfo, RawPatternInfo

View file

@ -1,11 +1,12 @@
from pathlib import Path
from typing import Any, Generic, TypeVar
from common.model import AnyContext
from common.properties import Properties
from hexcasting.pattern import PatternInfo
from minecraft.resource import ResourceLocation
from patchouli.book import Book
from patchouli.context import AnyBookContext, BookContext
from hexdoc.patchouli import AnyBookContext, Book, BookContext
from hexdoc.properties import Properties
from hexdoc.resource import ResourceLocation
from hexdoc.utils import AnyContext
from .pattern import Direction, PatternInfo
class HexContext(BookContext):
@ -15,7 +16,7 @@ class HexContext(BookContext):
AnyHexContext = TypeVar("AnyHexContext", bound=HexContext)
class HexBookModel(
class HexBookType(
Generic[AnyContext, AnyBookContext, AnyHexContext],
Book[AnyHexContext, AnyHexContext],
):
@ -28,7 +29,7 @@ class HexBookModel(
signatures = dict[str, PatternInfo]() # just for duplicate checking
for stub in props.pattern_stubs:
# for each stub, load all the patterns in the file
for pattern in stub.load_patterns(props.modid, props.pattern_regex):
for pattern in cls.load_patterns(stub, props):
# check for duplicates, because why not
if duplicate := (
patterns.get(pattern.id) or signatures.get(pattern.signature)
@ -45,5 +46,19 @@ class HexBookModel(
"patterns": patterns,
}
@classmethod
def load_patterns(cls, path: Path, props: Properties):
# TODO: add Gradle task to generate json with this data. this is dumb and fragile.
stub_text = path.read_text("utf-8")
for match in props.pattern_regex.finditer(stub_text):
signature, startdir, name, is_per_world = match.groups()
yield PatternInfo(
startdir=Direction[startdir],
signature=signature,
is_per_world=bool(is_per_world),
id=ResourceLocation(props.modid, name),
)
HexBook = HexBookModel[HexContext, HexContext, HexContext]
# type alias for convenience
HexBook = HexBookType[HexContext, HexContext, HexContext]

View file

@ -1,15 +1,15 @@
from typing import Any, Literal
from common.model import HexDocModel
from hexcasting.hex_book import HexContext
from minecraft.i18n import LocalizedItem
from minecraft.recipe import (
from hexdoc.minecraft import LocalizedItem, Recipe
from hexdoc.minecraft.recipe import (
ItemIngredient,
MinecraftItemIdIngredient,
MinecraftItemTagIngredient,
Recipe,
)
from minecraft.resource import ResourceLocation
from hexdoc.resource import ResourceLocation
from hexdoc.utils import HexDocModel
from .hex_book import HexContext
# ingredients

View file

@ -0,0 +1,20 @@
__all__ = [
"PageWithOpPattern",
"PageWithPattern",
"BrainsweepPage",
"CraftingMultiPage",
"LookupPatternPage",
"ManualOpPatternPage",
"ManualPatternNosigPage",
"ManualRawPatternPage",
]
from .abstract_hex_pages import PageWithOpPattern, PageWithPattern
from .hex_pages import (
BrainsweepPage,
CraftingMultiPage,
LookupPatternPage,
ManualOpPatternPage,
ManualPatternNosigPage,
ManualRawPatternPage,
)

View file

@ -3,12 +3,12 @@ from typing import Any, cast
from pydantic import ValidationInfo, model_validator
from hexcasting.pattern import RawPatternInfo
from minecraft.i18n import LocalizedStr
from minecraft.resource import ResourceLocation
from patchouli.page import PageWithText
from hexdoc.minecraft import LocalizedStr
from hexdoc.patchouli.page import PageWithText
from hexdoc.resource import ResourceLocation
from .hex_book import AnyHexContext, HexContext
from ..hex_book import AnyHexContext, HexContext
from ..pattern import RawPatternInfo
# TODO: make anchor required (breaks because of Greater Sentinel)

View file

@ -2,15 +2,14 @@ from typing import Any, cast
from pydantic import ValidationInfo, model_validator
from minecraft.i18n import LocalizedStr
from minecraft.recipe import CraftingRecipe
from minecraft.resource import ResourceLocation
from patchouli.page import PageWithText
from patchouli.page.abstract_pages import PageWithTitle
from hexdoc.minecraft import LocalizedStr
from hexdoc.minecraft.recipe import CraftingRecipe
from hexdoc.patchouli.page import PageWithText, PageWithTitle
from hexdoc.resource import ResourceLocation
from ..hex_book import HexContext
from ..hex_recipes import BrainsweepRecipe
from .abstract_hex_pages import PageWithOpPattern, PageWithPattern
from .hex_book import HexContext
from .hex_recipes import BrainsweepRecipe
class LookupPatternPage(

View file

@ -0,0 +1,45 @@
from enum import Enum
from typing import Annotated, Any
from pydantic import BeforeValidator
from hexdoc.resource import ResourceLocation
from hexdoc.utils import HexDocModel
class Direction(Enum):
NORTH_EAST = 0
EAST = 1
SOUTH_EAST = 2
SOUTH_WEST = 3
WEST = 4
NORTH_WEST = 5
@classmethod
def validate(cls, value: str | int | Any):
match value:
case str():
return cls[value]
case int():
return cls(value)
case _:
return value
DirectionField = Annotated[Direction, BeforeValidator(Direction.validate)]
class RawPatternInfo(HexDocModel[Any]):
startdir: DirectionField
signature: str
is_per_world: bool = False
q: int | None = None
r: int | None = None
class PatternInfo(RawPatternInfo):
id: ResourceLocation
@property
def name(self):
return self.id.path

View file

@ -0,0 +1,9 @@
__all__ = [
"I18n",
"LocalizedItem",
"LocalizedStr",
"Recipe",
]
from .i18n import I18n, LocalizedItem, LocalizedStr
from .recipe import Recipe

View file

@ -1,7 +1,7 @@
from __future__ import annotations
from dataclasses import InitVar
from functools import total_ordering
from pathlib import Path
from typing import Any, Callable, Self, cast
from pydantic import ValidationInfo, model_validator
@ -9,10 +9,10 @@ from pydantic.dataclasses import dataclass
from pydantic.functional_validators import ModelWrapValidatorHandler
from typing_extensions import TypedDict
from common.deserialize import isinstance_or_raise, load_json
from common.model import DEFAULT_CONFIG, HexDocModel
from common.properties import Properties
from minecraft.resource import ItemStack, ResourceLocation
from hexdoc.properties import Properties
from hexdoc.resource import ItemStack, ResourceLocation
from hexdoc.utils import DEFAULT_CONFIG, HexDocModel
from hexdoc.utils.deserialize import isinstance_or_raise, load_json
class I18nContext(TypedDict):
@ -94,12 +94,12 @@ class LocalizedItem(LocalizedStr):
class I18n:
"""Handles localization of strings."""
props: Properties
props: InitVar[Properties]
enabled: bool
lookup: dict[str, LocalizedStr] | None = None
def __post_init__(self):
def __post_init__(self, props: Properties):
# skip loading the files if we don't need to
self.lookup = None
if not self.enabled:
@ -109,8 +109,9 @@ class I18n:
# TODO: load ALL of the i18n files, return dict[str, _Lookup] | None
# or maybe dict[(str, str), LocalizedStr]
# we could also use that to ensure all i18n files have the same set of keys
path = self.dir / self.props.i18n.filename
raw_lookup = load_json(path) | (self.props.i18n.extra or {})
lang_dir = props.resources_dir / "assets" / props.modid / "lang"
path = lang_dir / props.i18n.filename
raw_lookup = load_json(path) | (props.i18n.extra or {})
# validate and insert
self.lookup = {}
@ -121,11 +122,6 @@ class I18n:
value=raw_value.replace("%%", "%"),
)
@property
def dir(self) -> Path:
"""eg. `resources/assets/hexcasting/lang`"""
return self.props.resources_dir / "assets" / self.props.modid / "lang"
def localize(
self,
*keys: str,

View file

@ -1,19 +1,19 @@
__all__ = [
"CraftingRecipe",
"ItemIngredient",
"MinecraftItemTagIngredient",
"MinecraftItemIdIngredient",
"ItemResult",
"Recipe",
"recipes",
"ItemIngredient",
"ItemIngredientOrList",
"MinecraftItemIdIngredient",
"MinecraftItemTagIngredient",
"CraftingRecipe",
"CraftingShapedRecipe",
"CraftingShapelessRecipe",
"ItemResult",
]
from .abstract_recipes import Recipe
from .ingredients import (
ItemIngredient,
ItemIngredientOrList,
MinecraftItemIdIngredient,
MinecraftItemTagIngredient,
)

View file

@ -2,13 +2,12 @@ from typing import Any, Self, cast
from pydantic import ValidationInfo, model_validator
from common.deserialize import load_json
from common.tagged_union import TypeTaggedUnion
from minecraft.resource import ResourceLocation
from patchouli.context import AnyBookContext
from hexdoc.properties import AnyPropsContext
from hexdoc.resource import ResourceLocation, TypeTaggedUnion
from hexdoc.utils.deserialize import load_json
class Recipe(TypeTaggedUnion[AnyBookContext], group="hexdoc.Recipe", type=None):
class Recipe(TypeTaggedUnion[AnyPropsContext], group="hexdoc.Recipe", type=None):
id: ResourceLocation
group: str | None = None
@ -31,7 +30,7 @@ class Recipe(TypeTaggedUnion[AnyBookContext], group="hexdoc.Recipe", type=None):
id = values
# load the recipe
context = cast(AnyBookContext, info.context)
context = cast(AnyPropsContext, info.context)
for recipe_dir in context["props"].recipe_dirs:
# TODO: should this use id.namespace somewhere?
path = recipe_dir / f"{id.path}.json"

View file

@ -0,0 +1,23 @@
from typing import Any
from hexdoc.resource import ResourceLocation, TypeTaggedUnion
from hexdoc.utils import AnyContext, NoValue
class ItemIngredient(
TypeTaggedUnion[AnyContext],
group="hexdoc.ItemIngredient",
type=None,
):
pass
ItemIngredientOrList = ItemIngredient[AnyContext] | list[ItemIngredient[AnyContext]]
class MinecraftItemIdIngredient(ItemIngredient[Any], type=NoValue):
item: ResourceLocation
class MinecraftItemTagIngredient(ItemIngredient[Any], type=NoValue):
tag: ResourceLocation

View file

@ -1,30 +1,31 @@
from common.model import HexDocModel
from minecraft.i18n import LocalizedItem
from patchouli.context import BookContext
from typing import Any
from hexdoc.utils import HexDocModel
from ..i18n import LocalizedItem
from .abstract_recipes import Recipe
from .ingredients import ItemIngredientOrList
class ItemResult(HexDocModel[BookContext]):
class ItemResult(HexDocModel[Any]):
item: LocalizedItem
count: int | None = None
class CraftingShapedRecipe(
Recipe[BookContext],
Recipe[Any],
type="minecraft:crafting_shaped",
):
pattern: list[str]
key: dict[str, ItemIngredientOrList[BookContext]]
key: dict[str, ItemIngredientOrList[Any]]
result: ItemResult
class CraftingShapelessRecipe(
Recipe[BookContext],
Recipe[Any],
type="minecraft:crafting_shapeless",
):
ingredients: list[ItemIngredientOrList[BookContext]]
ingredients: list[ItemIngredientOrList[Any]]
result: ItemResult

View file

@ -0,0 +1,16 @@
__all__ = [
"Book",
"Category",
"Entry",
"Page",
"FormatTree",
"AnyBookContext",
"BookContext",
]
from .book import Book
from .category import Category
from .entry import Entry
from .model import AnyBookContext, BookContext
from .page import Page
from .text import FormatTree

View file

@ -2,16 +2,15 @@ from typing import Any, Generic, Literal, Self, cast
from pydantic import Field, ValidationInfo, model_validator
from common.deserialize import isinstance_or_raise, load_json
from common.model import AnyContext, HexDocModel
from common.properties import Properties
from common.types import Color
from minecraft.i18n import I18n, LocalizedStr
from minecraft.resource import ItemStack, ResLoc, ResourceLocation
from hexdoc.minecraft import I18n, LocalizedStr
from hexdoc.properties import Properties
from hexdoc.resource import ItemStack, ResLoc, ResourceLocation
from hexdoc.utils import AnyContext, Color, HexDocModel
from hexdoc.utils.deserialize import isinstance_or_raise, load_json
from .category import Category
from .context import AnyBookContext, BookContext
from .entry import Entry
from .model import AnyBookContext, BookContext
from .text import DEFAULT_MACROS, FormatTree

View file

@ -3,17 +3,17 @@ from typing import Self
from pydantic import Field
from common.properties import Properties
from common.types import Sortable, sorted_dict
from minecraft.i18n import LocalizedStr
from minecraft.resource import ItemStack, ResourceLocation
from hexdoc.minecraft import LocalizedStr
from hexdoc.properties import Properties
from hexdoc.resource import ItemStack, ResourceLocation
from hexdoc.utils.types import Sortable, sorted_dict
from .context import BookContext, BookModelFile
from .entry import Entry
from .model import BookContext, BookFileModel
from .text import FormatTree
class Category(BookModelFile[BookContext, BookContext], Sortable):
class Category(BookFileModel[BookContext, BookContext], Sortable):
"""Category with pages and localizations.
See: https://vazkiimods.github.io/Patchouli/docs/reference/category-json

View file

@ -3,16 +3,17 @@ from typing import cast
from pydantic import Field, ValidationInfo, model_validator
from common.properties import Properties
from common.types import Color, Sortable
from minecraft.i18n import LocalizedStr
from minecraft.resource import ItemStack, ResourceLocation
from hexdoc.minecraft import LocalizedStr
from hexdoc.properties import Properties
from hexdoc.resource import ItemStack, ResourceLocation
from hexdoc.utils import Color
from hexdoc.utils.types import Sortable
from .context import BookContext, BookModelFile
from .page import Page
from .model import BookContext, BookFileModel
from .page.pages import Page
class Entry(BookModelFile[BookContext, BookContext], Sortable):
class Entry(BookFileModel[BookContext, BookContext], Sortable):
"""Entry json file, with pages and localizations.
See: https://vazkiimods.github.io/Patchouli/docs/reference/entry-json

View file

@ -4,10 +4,11 @@ from typing import Any, Generic, TypeVar, cast, dataclass_transform
from pydantic import ValidationInfo, model_validator
from common.model import AnyContext, HexDocModelFile
from common.properties import Properties
from minecraft.resource import ResourceLocation
from patchouli.text import FormatContext
from hexdoc.properties import Properties
from hexdoc.resource import ResourceLocation
from hexdoc.utils import AnyContext, HexDocFileModel
from .text.formatting import FormatContext
class BookContext(FormatContext):
@ -18,9 +19,9 @@ AnyBookContext = TypeVar("AnyBookContext", bound=BookContext)
@dataclass_transform()
class BookModelFile(
class BookFileModel(
Generic[AnyContext, AnyBookContext],
HexDocModelFile[AnyBookContext],
HexDocFileModel[AnyBookContext],
ABC,
):
id: ResourceLocation

View file

@ -2,17 +2,17 @@ __all__ = [
"Page",
"PageWithText",
"PageWithTitle",
"CraftingPage",
"EmptyPage",
"EntityPage",
"ImagePage",
"LinkPage",
"MultiblockPage",
"QuestPage",
"RelationsPage",
"SmeltingPage",
"SpotlightPage",
"TextPage",
"ImagePage",
"CraftingPage",
"SmeltingPage",
"MultiblockPage",
"EntityPage",
"SpotlightPage",
"LinkPage",
"RelationsPage",
"QuestPage",
"EmptyPage",
]
from .abstract_pages import Page, PageWithText, PageWithTitle

View file

@ -3,11 +3,11 @@ from typing import Any, ClassVar, Self
from pydantic import model_validator
from pydantic.functional_validators import ModelWrapValidatorHandler
from common.tagged_union import TagValue, TypeTaggedUnion
from minecraft.i18n import LocalizedStr
from minecraft.resource import ResourceLocation
from hexdoc.minecraft import LocalizedStr
from hexdoc.resource import ResourceLocation, TypeTaggedUnion
from hexdoc.utils import TagValue
from ..context import AnyBookContext
from ..model import AnyBookContext
from ..text import FormatTree

View file

@ -1,10 +1,10 @@
from typing import Any
from minecraft.i18n import LocalizedItem, LocalizedStr
from minecraft.recipe import CraftingRecipe
from minecraft.resource import Entity, ItemStack, ResourceLocation
from patchouli.context import BookContext
from hexdoc.minecraft import LocalizedItem, LocalizedStr
from hexdoc.minecraft.recipe import CraftingRecipe
from hexdoc.resource import Entity, ItemStack, ResourceLocation
from ..model import BookContext
from ..text import FormatTree
from .abstract_pages import Page, PageWithText, PageWithTitle

View file

@ -0,0 +1,9 @@
__all__ = [
"FormatTree",
"HTMLElement",
"HTMLStream",
"DEFAULT_MACROS",
]
from .formatting import DEFAULT_MACROS, FormatTree
from .html import HTMLElement, HTMLStream

View file

@ -12,10 +12,11 @@ from pydantic import ValidationInfo, model_validator
from pydantic.dataclasses import dataclass
from pydantic.functional_validators import ModelWrapValidatorHandler
from common.model import DEFAULT_CONFIG, HexDocModel
from common.properties import Properties
from common.types import TryGetEnum
from minecraft.i18n import I18nContext, LocalizedStr
from hexdoc.minecraft import LocalizedStr
from hexdoc.minecraft.i18n import I18nContext
from hexdoc.properties import PropsContext
from hexdoc.utils import DEFAULT_CONFIG, HexDocModel
from hexdoc.utils.types import TryGetEnum
from .html import HTMLElement, HTMLStream
@ -251,9 +252,8 @@ class _CloseTag(HexDocModel[Any], frozen=True):
_FORMAT_RE = re.compile(r"\$\(([^)]*)\)")
class FormatContext(I18nContext):
class FormatContext(I18nContext, PropsContext):
macros: dict[str, str]
props: Properties
@dataclass(config=DEFAULT_CONFIG)

View file

@ -5,7 +5,7 @@ from dataclasses import dataclass
from html import escape
from typing import IO, Any
from minecraft.i18n import LocalizedStr
from hexdoc.minecraft import LocalizedStr
def attributes_to_str(attributes: dict[str, Any]):

View file

@ -1,6 +1,8 @@
from __future__ import annotations
import re
from pathlib import Path
from typing import Annotated, Any, Self
from typing import Annotated, Any, Self, TypeVar
from pydantic import (
AfterValidator,
@ -9,11 +11,11 @@ from pydantic import (
HttpUrl,
field_validator,
)
from typing_extensions import TypedDict
from common.model import HexDocModel
from common.toml_placeholders import load_toml
from hexcasting.pattern import PatternStubFile
from minecraft.resource import ResourceLocation
from hexdoc.resource import ResourceLocation
from hexdoc.utils.model import HexDocModel
from hexdoc.utils.toml_placeholders import load_toml
NoTrailingSlashHttpUrl = Annotated[
str,
@ -27,7 +29,7 @@ class PlatformProps(HexDocModel[Any]):
generated: Path
src: Path
package: Path
pattern_stubs: list[PatternStubFile] | None = None
pattern_stubs: list[Path] | None = None
class I18nProps(HexDocModel[Any]):
@ -112,7 +114,7 @@ class Properties(HexDocModel[Any]):
return platforms
@property
def pattern_stubs(self) -> list[PatternStubFile]:
def pattern_stubs(self) -> list[Path]:
return [
stub
for platform in self.platforms
@ -132,3 +134,10 @@ class Properties(HexDocModel[Any]):
f"default_recipe_dir must be a valid index of recipe_dirs (expected <={num_dirs - 1}, got {value})"
)
return value
class PropsContext(TypedDict):
props: Properties
AnyPropsContext = TypeVar("AnyPropsContext", bound=PropsContext)

View file

@ -1,5 +1,9 @@
# pyright: reportPrivateUsage=false
# this file is used by basically everything
# so if it's in literally any namespace, everything fucking dies from circular deps
# basically, just leave it here
import re
from pathlib import Path
from typing import Any, ClassVar, Self
@ -8,7 +12,13 @@ from pydantic import field_validator, model_serializer, model_validator
from pydantic.dataclasses import dataclass
from pydantic.functional_validators import ModelWrapValidatorHandler
from common.model import DEFAULT_CONFIG
from hexdoc.utils import (
DEFAULT_CONFIG,
AnyContext,
InternallyTaggedUnion,
NoValueType,
TagValue,
)
def _make_regex(count: bool = False, nbt: bool = False) -> re.Pattern[str]:
@ -118,3 +128,22 @@ class Entity(BaseResourceLocation, regex=_make_regex(nbt=True)):
if self.nbt is not None:
s += self.nbt
return s
class TypeTaggedUnion(InternallyTaggedUnion[AnyContext], key="type", value=None):
type: ResourceLocation | None
def __init_subclass__(
cls,
*,
group: str | None = None,
type: TagValue | None,
) -> None:
super().__init_subclass__(group=group, value=type)
match type:
case str():
cls.type = ResourceLocation.from_str(type)
case NoValueType():
cls.type = None
case None:
pass

View file

@ -1,7 +1,6 @@
# because Tap.add_argument isn't typed, for some reason
# pyright: reportUnknownMemberType=false
import sys
from pathlib import Path
from jinja2 import Environment, FileSystemLoader, StrictUndefined
@ -9,12 +8,9 @@ from jinja2 import Environment, FileSystemLoader, StrictUndefined
# from jinja2.sandbox import SandboxedEnvironment
from tap import Tap
from common.jinja_extensions import IncludeRawExtension, hexdoc_block, hexdoc_wrap
from common.properties import Properties
from hexcasting.hex_book import HexBook
if sys.version_info < (3, 11):
raise RuntimeError("Minimum Python version: 3.11")
from hexdoc.hexcasting import HexBook
from hexdoc.properties import Properties
from hexdoc.utils.jinja_extensions import IncludeRawExtension, hexdoc_block, hexdoc_wrap
def strip_empty_lines(text: str) -> str:
@ -33,7 +29,11 @@ class Args(Tap):
self.add_argument("-o", "--output_file", required=False)
def main(args: Args) -> None:
def main(args: Args | None = None) -> None:
# allow passing Args for test cases, but parse by default
if args is None:
args = Args().parse_args()
# load the properties and book
props = Properties.load(args.properties_file)
book = HexBook.load(*HexBook.prepare(props))
@ -74,4 +74,4 @@ def main(args: Args) -> None:
# entry point: just read the CLI args and pass them to the actual logic
if __name__ == "__main__":
main(Args().parse_args())
main()

View file

@ -0,0 +1,22 @@
__all__ = [
"HexDocModel",
"FrozenHexDocModel",
"HexDocFileModel",
"InternallyTaggedUnion",
"Color",
"AnyContext",
"DEFAULT_CONFIG",
"NoValue",
"NoValueType",
"TagValue",
]
from .model import (
DEFAULT_CONFIG,
AnyContext,
FrozenHexDocModel,
HexDocFileModel,
HexDocModel,
)
from .tagged_union import InternallyTaggedUnion, NoValue, NoValueType, TagValue
from .types import Color

View file

@ -6,6 +6,7 @@ _T = TypeVar("_T")
_DEFAULT_MESSAGE = "Expected any of {expected}, got {actual}: {value}"
# there may well be a better way to do this but i don't know what it is
def isinstance_or_raise(
val: Any,

View file

@ -5,9 +5,9 @@ from jinja2.ext import Extension
from jinja2.parser import Parser
from markupsafe import Markup
from minecraft.i18n import LocalizedStr
from patchouli.text.formatting import FormatTree
from patchouli.text.html import HTMLStream
from hexdoc.minecraft import LocalizedStr
from hexdoc.patchouli import FormatTree
from hexdoc.patchouli.text import HTMLStream
# https://stackoverflow.com/a/64392515

View file

@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any, Generic, Self, TypeVar, dataclass_transfo
from pydantic import BaseModel, ConfigDict
from typing_extensions import TypedDict
from common.deserialize import load_json
from .deserialize import load_json
if TYPE_CHECKING:
from pydantic.root_model import Model
@ -53,7 +53,7 @@ class FrozenHexDocModel(Generic[AnyContext], HexDocModel[AnyContext]):
@dataclass_transform()
class HexDocModelFile(HexDocModel[AnyContext]):
class HexDocFileModel(HexDocModel[AnyContext]):
@classmethod
def load(cls, path: Path, context: AnyContext) -> Self:
data = load_json(path) | {"__path": path}

View file

@ -10,10 +10,11 @@ from pkg_resources import iter_entry_points
from pydantic import ValidationInfo, model_validator
from pydantic.functional_validators import ModelWrapValidatorHandler
from minecraft.resource import ResourceLocation
from .model import AnyContext, HexDocModel
# from hexdoc.minecraft import ResourceLocation
if TYPE_CHECKING:
from pydantic.root_model import Model
@ -154,13 +155,13 @@ class InternallyTaggedUnion(HexDocModel[AnyContext]):
context: AnyContext | None = None,
) -> Model:
# resolve forward references, because apparently we need to do this
if cls not in _rebuilt_models:
_rebuilt_models.add(cls)
cls.model_rebuild(
_types_namespace={
"ResourceLocation": ResourceLocation,
}
)
# if cls not in _rebuilt_models:
# _rebuilt_models.add(cls)
# cls.model_rebuild(
# _types_namespace={
# "ResourceLocation": ResourceLocation,
# }
# )
return super().model_validate(
obj,
@ -232,22 +233,3 @@ class InternallyTaggedUnion(HexDocModel[AnyContext]):
f"Failed to match {cls} with {cls._tag_key}={tag_value} to any of {tag_types}: {data}",
exceptions,
)
class TypeTaggedUnion(InternallyTaggedUnion[AnyContext], key="type", value=None):
type: ResourceLocation | None
def __init_subclass__(
cls,
*,
group: str | None = None,
type: TagValue | None,
) -> None:
super().__init_subclass__(group=group, value=type)
match type:
case str():
cls.type = ResourceLocation.from_str(type)
case NoValueType():
cls.type = None
case None:
pass

View file

@ -1,11 +1,10 @@
import datetime
import re
import tomllib
from pathlib import Path
from typing import Callable, TypeVar
import tomllib
from common.deserialize import isinstance_or_raise
from .deserialize import isinstance_or_raise
# TODO: there's (figuratively) literally no comments in this file

View file

@ -6,7 +6,7 @@ from typing import Any, Mapping, Protocol, TypeVar
from pydantic import field_validator, model_validator
from pydantic.dataclasses import dataclass
from common.model import DEFAULT_CONFIG
from .model import DEFAULT_CONFIG
_T = TypeVar("_T")

View file

@ -1,24 +0,0 @@
from common.tagged_union import NoValue, TypeTaggedUnion
from minecraft.resource import ResourceLocation
from patchouli.context import AnyBookContext, BookContext
class ItemIngredient(
TypeTaggedUnion[AnyBookContext],
group="hexdoc.ItemIngredient",
type=None,
):
pass
ItemIngredientOrList = (
ItemIngredient[AnyBookContext] | list[ItemIngredient[AnyBookContext]]
)
class MinecraftItemIdIngredient(ItemIngredient[BookContext], type=NoValue):
item: ResourceLocation
class MinecraftItemTagIngredient(ItemIngredient[BookContext], type=NoValue):
tag: ResourceLocation

View file

@ -1,7 +0,0 @@
__all__ = ["Book", "Category", "Entry", "Page", "Style", "FormatTree"]
from .book import Book
from .category import Category
from .entry import Entry
from .page import Page
from .text import FormatTree, Style

View file

@ -1,8 +0,0 @@
__all__ = [
"FormatTree",
"Style",
"DEFAULT_MACROS",
"FormatContext",
]
from .formatting import DEFAULT_MACROS, FormatContext, FormatTree, Style

View file

@ -1,6 +1,6 @@
import pytest
from minecraft.resource import ItemStack, ResLoc, ResourceLocation
from hexdoc.resource import ItemStack, ResLoc, ResourceLocation
resource_locations: list[tuple[str, ResourceLocation, str]] = [
(

View file

@ -1,34 +1,13 @@
import subprocess
import sys
from pathlib import Path
from typing import Iterator
import pytest
from bs4 import BeautifulSoup as bs
from syrupy.assertion import SnapshotAssertion
from syrupy.extensions.amber import AmberSnapshotExtension
from syrupy.types import SerializedData
from hexcasting.scripts.main import Args, main
from hexdoc.scripts.hexdoc import Args, main
def prettify(data: SerializedData) -> str:
return bs(data, features="html.parser").prettify()
class NoDiffSnapshotEx(AmberSnapshotExtension):
def diff_snapshots(
self, serialized_data: SerializedData, snapshot_data: SerializedData
) -> SerializedData:
return "no diff"
def diff_lines(
self, serialized_data: SerializedData, snapshot_data: SerializedData
) -> Iterator[str]:
yield from ["no diff"]
_RUN = [sys.executable, "-m" "hexcasting.scripts.main"]
_RUN = ["hexdoc"]
_ARGV = ["properties.toml", "-o"]
@ -53,28 +32,3 @@ def test_cmd(tmp_path: Path, snapshot: SnapshotAssertion):
def test_stdout(capsys: pytest.CaptureFixture[str], snapshot: SnapshotAssertion):
main(Args().parse_args(["properties.toml"]))
assert capsys.readouterr() == snapshot
# def test_book_text(snapshot: SnapshotAssertion):
# def test_field(data_class: Any, field: Field[Any]):
# value = getattr(data_class, field.name, None)
# if isinstance(value, (LocalizedStr, FormatTree)):
# assert value == snapshot
# props = Properties.load(Path("properties.toml"))
# book = HexBook.load(HexBookState(props))
# for field in fields(book):
# test_field(book, field)
# for category in book.categories.values():
# for field in fields(category):
# test_field(category, field)
# for entry in category.entries:
# for field in fields(entry):
# test_field(entry, field)
# for page in entry.pages:
# for field in fields(page):
# test_field(page, field)

View file

@ -1,6 +1,6 @@
import pytest
from common.types import Color
from hexdoc.utils.types import Color
colors: list[str] = [
"#0099FF",

View file

@ -1,6 +1,6 @@
# pyright: reportPrivateUsage=false
from patchouli.text import DEFAULT_MACROS, FormatTree
from patchouli.text.formatting import (
from hexdoc.patchouli.text import DEFAULT_MACROS, FormatTree
from hexdoc.patchouli.text.formatting import (
CommandStyle,
FunctionStyle,
FunctionStyleType,