We do a bit of renaming

This commit is contained in:
object-Object 2023-07-10 00:56:40 -04:00
parent aeb737a4eb
commit 92f7770dc6
7 changed files with 155 additions and 159 deletions

View file

@ -3,6 +3,7 @@
modid = "hexcasting"
book_name = "thehexbook"
template = "template.html"
is_0_black = false
recipe_dirs = [
"{fabric.generated}/data/{modid}/recipes",
"{forge.generated}/data/{modid}/recipes",
@ -13,6 +14,9 @@ default_recipe_dir = 0
# more on that later
pattern_regex = {_Raw='HexPattern\.fromAngles\("([qweasd]+)", HexDir\.(\w+)\),\s*modLoc\("([^"]+)"\)([^;]*true\);)?'}
[base_asset_urls]
hexcasting = "https://raw.githubusercontent.com/gamma-delta/HexMod/main/Common/src/main/resources"
[i18n]
lang = "en_us"
filename = "{lang}.json"
@ -23,7 +27,6 @@ filename = "{lang}.json"
# platforms
# fabric and forge are optional
[common]
src = "../Common/src"

View file

@ -1,13 +1,25 @@
import re
from pathlib import Path
from typing import Any, Self
from typing import Annotated, Any, Self
from pydantic import Field, model_validator
from pydantic import (
AfterValidator,
Field,
FieldValidationInfo,
HttpUrl,
field_validator,
)
from common.model import HexDocModel
from common.toml_placeholders import load_toml
from hexcasting.pattern import PatternStubFile
NoTrailingSlashHttpUrl = Annotated[
str,
HttpUrl,
AfterValidator(lambda u: str(u).rstrip("/")),
]
class PlatformProps(HexDocModel[Any]):
resources: Path
@ -27,15 +39,17 @@ class Properties(HexDocModel[Any]):
modid: str
book_name: str
template: Path
is_0_black: bool
"""If true, the style `$(0)` changes the text color to black; otherwise it resets
the text color to the default."""
recipe_dirs: list[Path]
default_recipe_dir_index_: int = Field(alias="default_recipe_dir")
pattern_regex: re.Pattern[str]
is_0_black: bool = False
"""If true, the style `$(0)` changes the text color to black; otherwise it resets
the text color to the default."""
base_asset_urls: dict[str, NoTrailingSlashHttpUrl]
"""Mapping from modid to the url of that mod's `resources` directory on GitHub."""
i18n: I18nProps
@ -100,10 +114,11 @@ class Properties(HexDocModel[Any]):
for stub in platform.pattern_stubs
]
@model_validator(mode="after")
def _check_default_recipe_dir(self):
if self.default_recipe_dir_index_ >= len(self.recipe_dirs):
@field_validator("default_recipe_dir_index_")
def _check_default_recipe_dir(cls, value: int, info: FieldValidationInfo) -> int:
num_dirs = len(info.data["recipe_dirs"])
if value >= num_dirs:
raise ValueError(
f"default_recipe_dir must be a valid index of recipe_dirs (expected <={len(self.recipe_dirs)}, got {self.default_recipe_dir_index_})"
f"default_recipe_dir must be a valid index of recipe_dirs (expected <={num_dirs - 1}, got {value})"
)
return self
return value

View file

@ -11,7 +11,7 @@ from patchouli.page import PageWithTitle
from .hex_book import AnyHexContext, HexContext
# TODO: make anchor required
# TODO: make anchor required (breaks because of Greater Sentinel)
class PageWithPattern(PageWithTitle[AnyHexContext], ABC, type=None):
title_: None = Field(default=None, include=True)

View file

@ -2,6 +2,7 @@
import io
from typing import Any
from common.properties import Properties
from hexcasting import HexBook
from hexcasting.abstract_hex_pages import PageWithPattern
from hexcasting.hex_pages import BrainsweepPage, CraftingMultiPage, LookupPatternPage
@ -17,13 +18,7 @@ from patchouli.page import (
SpotlightPage,
TextPage,
)
from patchouli.text.tags import Stream
# extra info :(
# TODO: properties.toml
repo_names = {
"hexcasting": "https://raw.githubusercontent.com/gamma-delta/HexMod/main/Common/src/main/resources",
}
from patchouli.text.html import HTMLStream
def entry_spoilered(root_info: HexBook, entry: Entry):
@ -36,42 +31,42 @@ def category_spoilered(root_info: HexBook, category: Category):
return all(entry_spoilered(root_info, ent) for ent in category.entries)
def write_block(out: Stream, block: FormatTree | str | LocalizedStr):
def write_block(out: HTMLStream, block: FormatTree | str | LocalizedStr):
if isinstance(block, LocalizedStr):
block = str(block)
if isinstance(block, str):
first = False
for line in block.split("\n"):
if first:
out.tag("br")
out.self_closing_element("br")
first = True
out.text(line)
return
with block.style.tag(out):
with block.style.element(out):
for child in block.children:
write_block(out, child)
def anchor_toc(out: Stream):
with out.pair_tag(
def anchor_toc(out: HTMLStream):
with out.element(
"a", href="#table-of-contents", clazz="permalink small", title="Jump to top"
):
out.empty_pair_tag("i", clazz="bi bi-box-arrow-up")
out.empty_element("i", clazz="bi bi-box-arrow-up")
def permalink(out: Stream, link: str):
with out.pair_tag("a", href=link, clazz="permalink small", title="Permalink"):
out.empty_pair_tag("i", clazz="bi bi-link-45deg")
def permalink(out: HTMLStream, link: str):
with out.element("a", href=link, clazz="permalink small", title="Permalink"):
out.empty_element("i", clazz="bi bi-link-45deg")
def write_page(out: Stream, pageid: str, page: Page[Any]):
def write_page(out: HTMLStream, pageid: str, page: Page[Any], props: Properties):
if anchor := page.anchor:
anchor_id = pageid + "@" + anchor
else:
anchor_id = None
# TODO: put this in the page classes - this is just a stopgap to make the tests pass
with out.pair_tag_if(anchor_id, "div", id=anchor_id):
with out.element_if(anchor_id, "div", id=anchor_id):
if isinstance(page, PageWithTitle) and page.title is not None:
# gross
_kwargs = (
@ -79,7 +74,7 @@ def write_page(out: Stream, pageid: str, page: Page[Any]):
if isinstance(page, LookupPatternPage)
else {}
)
with out.pair_tag("h4", **_kwargs):
with out.element("h4", **_kwargs):
out.text(page.title)
if anchor_id:
permalink(out, "#" + anchor_id)
@ -89,69 +84,69 @@ def write_page(out: Stream, pageid: str, page: Page[Any]):
pass
case LinkPage():
write_block(out, page.text)
with out.pair_tag("h4", clazz="linkout"):
with out.pair_tag("a", href=page.url):
with out.element("h4", clazz="linkout"):
with out.element("a", href=page.url):
out.text(page.link_text)
case TextPage():
# LinkPage is a TextPage, so this needs to be below it
write_block(out, page.text)
case SpotlightPage():
with out.pair_tag("h4", clazz="spotlight-title page-header"):
with out.element("h4", clazz="spotlight-title page-header"):
out.text(page.item)
if page.text is not None:
write_block(out, page.text)
case CraftingPage():
with out.pair_tag("blockquote", clazz="crafting-info"):
with out.element("blockquote", clazz="crafting-info"):
out.text(f"Depicted in the book: The crafting recipe for the ")
first = True
for recipe in page.recipes:
if not first:
out.text(" and ")
first = False
with out.pair_tag("code"):
with out.element("code"):
out.text(recipe.result.item)
out.text(".")
if page.text is not None:
write_block(out, page.text)
case ImagePage():
with out.pair_tag("p", clazz="img-wrapper"):
with out.element("p", clazz="img-wrapper"):
for img in page.images:
# TODO: make a thing for this
out.empty_pair_tag(
out.empty_element(
"img",
src=f"{repo_names[img.namespace]}/assets/{img.namespace}/{img.path}",
src=f"{props.base_asset_urls[img.namespace]}/assets/{img.namespace}/{img.path}",
)
if page.text is not None:
write_block(out, page.text)
case CraftingMultiPage():
with out.pair_tag("blockquote", clazz="crafting-info"):
with out.element("blockquote", clazz="crafting-info"):
out.text(
f"Depicted in the book: Several crafting recipes, for the "
)
with out.pair_tag("code"):
with out.element("code"):
out.text(page.recipes[0].result.item)
for i in page.recipes[1:]:
out.text(", ")
with out.pair_tag("code"):
with out.element("code"):
out.text(i.result.item)
out.text(".")
if page.text is not None:
write_block(out, page.text)
case BrainsweepPage():
with out.pair_tag("blockquote", clazz="crafting-info"):
with out.element("blockquote", clazz="crafting-info"):
out.text(
f"Depicted in the book: A mind-flaying recipe producing the "
)
with out.pair_tag("code"):
with out.element("code"):
out.text(page.recipe.result.name)
out.text(".")
if page.text is not None:
write_block(out, page.text)
case PageWithPattern():
with out.pair_tag("details", clazz="spell-collapsible"):
out.empty_pair_tag("summary", clazz="collapse-spell")
with out.element("details", clazz="spell-collapsible"):
out.empty_element("summary", clazz="collapse-spell")
for pattern in page.patterns:
with out.pair_tag(
with out.element(
"canvas",
clazz="spell-viz",
width=216,
@ -166,30 +161,30 @@ def write_page(out: Stream, pageid: str, page: Page[Any]):
)
write_block(out, page.text)
case _:
with out.pair_tag("p", clazz="todo-note"):
with out.element("p", clazz="todo-note"):
out.text(f"TODO: Missing processor for type: {type(page)}")
if isinstance(page, PageWithText):
write_block(out, page.text)
out.tag("br")
out.self_closing_element("br")
def write_entry(out: Stream, book: HexBook, entry: Entry):
with out.pair_tag("div", id=entry.id.path):
with out.pair_tag_if(entry_spoilered(book, entry), "div", clazz="spoilered"):
with out.pair_tag("h3", clazz="entry-title page-header"):
def write_entry(out: HTMLStream, book: HexBook, entry: Entry):
with out.element("div", id=entry.id.path):
with out.element_if(entry_spoilered(book, entry), "div", clazz="spoilered"):
with out.element("h3", clazz="entry-title page-header"):
write_block(out, entry.name)
anchor_toc(out)
permalink(out, entry.id.href)
for page in entry.pages:
write_page(out, entry.id.path, page)
write_page(out, entry.id.path, page, book.context["props"])
def write_category(out: Stream, book: HexBook, category: Category):
with out.pair_tag("section", id=category.id.path):
with out.pair_tag_if(
def write_category(out: HTMLStream, book: HexBook, category: Category):
with out.element("section", id=category.id.path):
with out.element_if(
category_spoilered(book, category), "div", clazz="spoilered"
):
with out.pair_tag("h2", clazz="category-title page-header"):
with out.element("h2", clazz="category-title page-header"):
write_block(out, category.name)
anchor_toc(out)
permalink(out, category.id.href)
@ -199,31 +194,31 @@ def write_category(out: Stream, book: HexBook, category: Category):
write_entry(out, book, entry)
def write_toc(out: Stream, book: HexBook):
with out.pair_tag("h2", id="table-of-contents", clazz="page-header"):
def write_toc(out: HTMLStream, book: HexBook):
with out.element("h2", id="table-of-contents", clazz="page-header"):
out.text("Table of Contents")
with out.pair_tag(
with out.element(
"a",
href="javascript:void(0)",
clazz="permalink toggle-link small",
data_target="toc-category",
title="Toggle all",
):
out.empty_pair_tag("i", clazz="bi bi-list-nested")
out.empty_element("i", clazz="bi bi-list-nested")
permalink(out, "#table-of-contents")
for category in book.categories.values():
with out.pair_tag("details", clazz="toc-category"):
with out.pair_tag("summary"):
with out.pair_tag(
with out.element("details", clazz="toc-category"):
with out.element("summary"):
with out.element(
"a",
href=category.id.href,
clazz="spoilered" if category_spoilered(book, category) else "",
):
out.text(category.name)
with out.pair_tag("ul"):
with out.element("ul"):
for entry in category.entries:
with out.pair_tag("li"):
with out.pair_tag(
with out.element("li"):
with out.element(
"a",
href=entry.id.href,
clazz="spoilered" if entry_spoilered(book, entry) else "",
@ -231,15 +226,15 @@ def write_toc(out: Stream, book: HexBook):
out.text(entry.name)
def write_book(out: Stream, book: HexBook):
with out.pair_tag("div", clazz="container"):
with out.pair_tag("header", clazz="jumbotron"):
with out.pair_tag("h1", clazz="book-title"):
def write_book(out: HTMLStream, book: HexBook):
with out.element("div", clazz="container"):
with out.element("header", clazz="jumbotron"):
with out.element("h1", clazz="book-title"):
write_block(out, book.name)
write_block(out, book.landing_text)
with out.pair_tag("nav"):
with out.element("nav"):
write_toc(out, book)
with out.pair_tag("main", clazz="book-body"):
with out.element("main", clazz="book-body"):
for category in book.categories.values():
write_category(out, book, category)
@ -258,7 +253,7 @@ def generate_docs(book: HexBook, template: str) -> str:
_, *spoilers = line.split()
book.context["spoilers"].update(spoilers)
elif line == "#DUMP_BODY_HERE\n":
write_book(Stream(output), book)
write_book(HTMLStream(output), book)
print("", file=output)
else:
print(line, end="", file=output)

View file

@ -17,7 +17,7 @@ from common.properties import Properties
from common.types import TryGetEnum
from minecraft.i18n import I18nContext, LocalizedStr
from .tags import PairTag, Stream
from .html import HTMLElement, HTMLStream
DEFAULT_MACROS = {
"$(obf)": "$(k)",
@ -167,27 +167,27 @@ class Style(ABC, HexDocModel[Any], frozen=True):
raise ValueError(f"Unhandled style: {style_str}")
@abstractmethod
def tag(self, out: Stream) -> PairTag | nullcontext[None]:
def element(self, out: HTMLStream) -> HTMLElement | nullcontext[None]:
...
class CommandStyle(Style, frozen=True):
type: CommandStyleType | BaseStyleType
def tag(self, out: Stream) -> PairTag | nullcontext[None]:
def element(self, out: HTMLStream) -> HTMLElement | nullcontext[None]:
match self.type:
case CommandStyleType.obfuscated:
return out.pair_tag("span", clazz="obfuscated")
return out.element("span", clazz="obfuscated")
case CommandStyleType.bold:
return out.pair_tag("strong")
return out.element("strong")
case CommandStyleType.strikethrough:
return out.pair_tag("s")
return out.element("s")
case CommandStyleType.underline:
return out.pair_tag("span", style="text-decoration: underline")
return out.element("span", style="text-decoration: underline")
case CommandStyleType.italic:
return out.pair_tag("i")
return out.element("i")
case SpecialStyleType.base:
return out.null_tag()
return nullcontext()
class ParagraphStyle(Style, frozen=True):
@ -212,8 +212,8 @@ class ParagraphStyle(Style, frozen=True):
def list_item(cls) -> Self:
return cls(attributes={"clazz": "fake-li"})
def tag(self, out: Stream) -> PairTag:
return out.pair_tag("p", **self.attributes)
def element(self, out: HTMLStream) -> HTMLElement:
return out.element("p", **self.attributes)
def _format_href(value: str) -> str:
@ -226,20 +226,20 @@ class FunctionStyle(Style, frozen=True):
type: FunctionStyleType | ColorStyleType
value: str
def tag(self, out: Stream) -> PairTag:
def element(self, out: HTMLStream) -> HTMLElement:
match self.type:
case FunctionStyleType.link:
return out.pair_tag("a", href=_format_href(self.value))
return out.element("a", href=_format_href(self.value))
case FunctionStyleType.tooltip:
return out.pair_tag("span", clazz="has-tooltip", title=self.value)
return out.element("span", clazz="has-tooltip", title=self.value)
case FunctionStyleType.cmd_click:
return out.pair_tag(
return out.element(
"span",
clazz="has-cmd_click",
title=f"When clicked, would execute: {self.value}",
)
case SpecialStyleType.color:
return out.pair_tag("span", style=f"color: #{self.value}")
return out.element("span", style=f"color: #{self.value}")
# intentionally not inheriting from Style, because this is basically an implementation

View file

@ -0,0 +1,51 @@
# TODO: type
from contextlib import nullcontext
from dataclasses import dataclass
from html import escape
from typing import IO, Any
from minecraft.i18n import LocalizedStr
def attributes_to_str(attributes: dict[str, Any]):
return "".join(
f" {'class' if key == 'clazz' else key.replace('_', '-')}={repr(escape(str(value)))}"
for key, value in attributes.items()
)
@dataclass
class HTMLElement:
stream: IO[str]
name: str
attributes: dict[str, Any]
def __enter__(self) -> None:
self.stream.write(f"<{self.name}{attributes_to_str(self.attributes)}>")
def __exit__(self, *_: Any) -> None:
self.stream.write(f"</{self.name}>")
@dataclass
class HTMLStream:
stream: IO[str]
def self_closing_element(self, name: str, **kwargs: Any):
keywords = attributes_to_str(kwargs)
self.stream.write(f"<{name}{keywords} />")
return self
def element(self, name: str, **kwargs: Any):
return HTMLElement(self.stream, name, kwargs)
def element_if(self, cond: Any, name: str, **kwargs: Any):
return self.element(name, **kwargs) if cond else nullcontext()
def empty_element(self, name: str, **kwargs: Any):
with self.element(name, **kwargs):
pass
def text(self, txt: str | LocalizedStr):
self.stream.write(escape(str(txt)))
return self

View file

@ -1,68 +0,0 @@
# TODO: type
from contextlib import nullcontext
from dataclasses import InitVar, dataclass
from html import escape
from typing import IO, Any
from minecraft.i18n import LocalizedStr
def tag_args(kwargs: dict[str, Any]):
return "".join(
f" {'class' if key == 'clazz' else key.replace('_', '-')}={repr(escape(str(value)))}"
for key, value in kwargs.items()
)
@dataclass
class PairTag:
stream: IO[str]
name: str
args: InitVar[dict[str, Any]]
def __post_init__(self, args: dict[str, Any]):
self.args_str = tag_args(args)
def __enter__(self):
# TODO: self.stream.write??????????
print(f"<{self.name}{self.args_str}>", file=self.stream, end="")
def __exit__(self, _1: Any, _2: Any, _3: Any):
print(f"</{self.name}>", file=self.stream, end="")
class Empty:
def __enter__(self):
pass
def __exit__(self, _1: Any, _2: Any, _3: Any):
pass
class Stream:
__slots__ = ["stream"]
def __init__(self, stream: IO[str]):
self.stream = stream
def tag(self, name: str, **kwargs: Any):
keywords = tag_args(kwargs)
print(f"<{name}{keywords} />", file=self.stream, end="")
return self
def pair_tag(self, name: str, **kwargs: Any):
return PairTag(self.stream, name, kwargs)
def pair_tag_if(self, cond: Any, name: str, **kwargs: Any):
return self.pair_tag(name, **kwargs) if cond else Empty()
def empty_pair_tag(self, name: str, **kwargs: Any):
with self.pair_tag(name, **kwargs):
pass
def null_tag(self):
return nullcontext()
def text(self, txt: str | LocalizedStr):
print(escape(str(txt)), file=self.stream, end="")
return self