We do a bit of renaming
This commit is contained in:
parent
aeb737a4eb
commit
92f7770dc6
7 changed files with 155 additions and 159 deletions
|
@ -3,6 +3,7 @@
|
||||||
modid = "hexcasting"
|
modid = "hexcasting"
|
||||||
book_name = "thehexbook"
|
book_name = "thehexbook"
|
||||||
template = "template.html"
|
template = "template.html"
|
||||||
|
is_0_black = false
|
||||||
recipe_dirs = [
|
recipe_dirs = [
|
||||||
"{fabric.generated}/data/{modid}/recipes",
|
"{fabric.generated}/data/{modid}/recipes",
|
||||||
"{forge.generated}/data/{modid}/recipes",
|
"{forge.generated}/data/{modid}/recipes",
|
||||||
|
@ -13,6 +14,9 @@ default_recipe_dir = 0
|
||||||
# more on that later
|
# more on that later
|
||||||
pattern_regex = {_Raw='HexPattern\.fromAngles\("([qweasd]+)", HexDir\.(\w+)\),\s*modLoc\("([^"]+)"\)([^;]*true\);)?'}
|
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]
|
[i18n]
|
||||||
lang = "en_us"
|
lang = "en_us"
|
||||||
filename = "{lang}.json"
|
filename = "{lang}.json"
|
||||||
|
@ -23,7 +27,6 @@ filename = "{lang}.json"
|
||||||
|
|
||||||
|
|
||||||
# platforms
|
# platforms
|
||||||
# fabric and forge are optional
|
|
||||||
|
|
||||||
[common]
|
[common]
|
||||||
src = "../Common/src"
|
src = "../Common/src"
|
||||||
|
|
|
@ -1,13 +1,25 @@
|
||||||
import re
|
import re
|
||||||
from pathlib import Path
|
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.model import HexDocModel
|
||||||
from common.toml_placeholders import load_toml
|
from common.toml_placeholders import load_toml
|
||||||
from hexcasting.pattern import PatternStubFile
|
from hexcasting.pattern import PatternStubFile
|
||||||
|
|
||||||
|
NoTrailingSlashHttpUrl = Annotated[
|
||||||
|
str,
|
||||||
|
HttpUrl,
|
||||||
|
AfterValidator(lambda u: str(u).rstrip("/")),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class PlatformProps(HexDocModel[Any]):
|
class PlatformProps(HexDocModel[Any]):
|
||||||
resources: Path
|
resources: Path
|
||||||
|
@ -27,15 +39,17 @@ class Properties(HexDocModel[Any]):
|
||||||
modid: str
|
modid: str
|
||||||
book_name: str
|
book_name: str
|
||||||
template: Path
|
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]
|
recipe_dirs: list[Path]
|
||||||
default_recipe_dir_index_: int = Field(alias="default_recipe_dir")
|
default_recipe_dir_index_: int = Field(alias="default_recipe_dir")
|
||||||
|
|
||||||
pattern_regex: re.Pattern[str]
|
pattern_regex: re.Pattern[str]
|
||||||
|
|
||||||
is_0_black: bool = False
|
base_asset_urls: dict[str, NoTrailingSlashHttpUrl]
|
||||||
"""If true, the style `$(0)` changes the text color to black; otherwise it resets
|
"""Mapping from modid to the url of that mod's `resources` directory on GitHub."""
|
||||||
the text color to the default."""
|
|
||||||
|
|
||||||
i18n: I18nProps
|
i18n: I18nProps
|
||||||
|
|
||||||
|
@ -100,10 +114,11 @@ class Properties(HexDocModel[Any]):
|
||||||
for stub in platform.pattern_stubs
|
for stub in platform.pattern_stubs
|
||||||
]
|
]
|
||||||
|
|
||||||
@model_validator(mode="after")
|
@field_validator("default_recipe_dir_index_")
|
||||||
def _check_default_recipe_dir(self):
|
def _check_default_recipe_dir(cls, value: int, info: FieldValidationInfo) -> int:
|
||||||
if self.default_recipe_dir_index_ >= len(self.recipe_dirs):
|
num_dirs = len(info.data["recipe_dirs"])
|
||||||
|
if value >= num_dirs:
|
||||||
raise ValueError(
|
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
|
||||||
|
|
|
@ -11,7 +11,7 @@ from patchouli.page import PageWithTitle
|
||||||
from .hex_book import AnyHexContext, HexContext
|
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):
|
class PageWithPattern(PageWithTitle[AnyHexContext], ABC, type=None):
|
||||||
title_: None = Field(default=None, include=True)
|
title_: None = Field(default=None, include=True)
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import io
|
import io
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from common.properties import Properties
|
||||||
from hexcasting import HexBook
|
from hexcasting import HexBook
|
||||||
from hexcasting.abstract_hex_pages import PageWithPattern
|
from hexcasting.abstract_hex_pages import PageWithPattern
|
||||||
from hexcasting.hex_pages import BrainsweepPage, CraftingMultiPage, LookupPatternPage
|
from hexcasting.hex_pages import BrainsweepPage, CraftingMultiPage, LookupPatternPage
|
||||||
|
@ -17,13 +18,7 @@ from patchouli.page import (
|
||||||
SpotlightPage,
|
SpotlightPage,
|
||||||
TextPage,
|
TextPage,
|
||||||
)
|
)
|
||||||
from patchouli.text.tags import Stream
|
from patchouli.text.html import HTMLStream
|
||||||
|
|
||||||
# extra info :(
|
|
||||||
# TODO: properties.toml
|
|
||||||
repo_names = {
|
|
||||||
"hexcasting": "https://raw.githubusercontent.com/gamma-delta/HexMod/main/Common/src/main/resources",
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def entry_spoilered(root_info: HexBook, entry: Entry):
|
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)
|
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):
|
if isinstance(block, LocalizedStr):
|
||||||
block = str(block)
|
block = str(block)
|
||||||
if isinstance(block, str):
|
if isinstance(block, str):
|
||||||
first = False
|
first = False
|
||||||
for line in block.split("\n"):
|
for line in block.split("\n"):
|
||||||
if first:
|
if first:
|
||||||
out.tag("br")
|
out.self_closing_element("br")
|
||||||
first = True
|
first = True
|
||||||
out.text(line)
|
out.text(line)
|
||||||
return
|
return
|
||||||
with block.style.tag(out):
|
with block.style.element(out):
|
||||||
for child in block.children:
|
for child in block.children:
|
||||||
write_block(out, child)
|
write_block(out, child)
|
||||||
|
|
||||||
|
|
||||||
def anchor_toc(out: Stream):
|
def anchor_toc(out: HTMLStream):
|
||||||
with out.pair_tag(
|
with out.element(
|
||||||
"a", href="#table-of-contents", clazz="permalink small", title="Jump to top"
|
"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):
|
def permalink(out: HTMLStream, link: str):
|
||||||
with out.pair_tag("a", href=link, clazz="permalink small", title="Permalink"):
|
with out.element("a", href=link, clazz="permalink small", title="Permalink"):
|
||||||
out.empty_pair_tag("i", clazz="bi bi-link-45deg")
|
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:
|
if anchor := page.anchor:
|
||||||
anchor_id = pageid + "@" + anchor
|
anchor_id = pageid + "@" + anchor
|
||||||
else:
|
else:
|
||||||
anchor_id = None
|
anchor_id = None
|
||||||
|
|
||||||
# TODO: put this in the page classes - this is just a stopgap to make the tests pass
|
# 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:
|
if isinstance(page, PageWithTitle) and page.title is not None:
|
||||||
# gross
|
# gross
|
||||||
_kwargs = (
|
_kwargs = (
|
||||||
|
@ -79,7 +74,7 @@ def write_page(out: Stream, pageid: str, page: Page[Any]):
|
||||||
if isinstance(page, LookupPatternPage)
|
if isinstance(page, LookupPatternPage)
|
||||||
else {}
|
else {}
|
||||||
)
|
)
|
||||||
with out.pair_tag("h4", **_kwargs):
|
with out.element("h4", **_kwargs):
|
||||||
out.text(page.title)
|
out.text(page.title)
|
||||||
if anchor_id:
|
if anchor_id:
|
||||||
permalink(out, "#" + anchor_id)
|
permalink(out, "#" + anchor_id)
|
||||||
|
@ -89,69 +84,69 @@ def write_page(out: Stream, pageid: str, page: Page[Any]):
|
||||||
pass
|
pass
|
||||||
case LinkPage():
|
case LinkPage():
|
||||||
write_block(out, page.text)
|
write_block(out, page.text)
|
||||||
with out.pair_tag("h4", clazz="linkout"):
|
with out.element("h4", clazz="linkout"):
|
||||||
with out.pair_tag("a", href=page.url):
|
with out.element("a", href=page.url):
|
||||||
out.text(page.link_text)
|
out.text(page.link_text)
|
||||||
case TextPage():
|
case TextPage():
|
||||||
# LinkPage is a TextPage, so this needs to be below it
|
# LinkPage is a TextPage, so this needs to be below it
|
||||||
write_block(out, page.text)
|
write_block(out, page.text)
|
||||||
case SpotlightPage():
|
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)
|
out.text(page.item)
|
||||||
if page.text is not None:
|
if page.text is not None:
|
||||||
write_block(out, page.text)
|
write_block(out, page.text)
|
||||||
case CraftingPage():
|
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 ")
|
out.text(f"Depicted in the book: The crafting recipe for the ")
|
||||||
first = True
|
first = True
|
||||||
for recipe in page.recipes:
|
for recipe in page.recipes:
|
||||||
if not first:
|
if not first:
|
||||||
out.text(" and ")
|
out.text(" and ")
|
||||||
first = False
|
first = False
|
||||||
with out.pair_tag("code"):
|
with out.element("code"):
|
||||||
out.text(recipe.result.item)
|
out.text(recipe.result.item)
|
||||||
out.text(".")
|
out.text(".")
|
||||||
if page.text is not None:
|
if page.text is not None:
|
||||||
write_block(out, page.text)
|
write_block(out, page.text)
|
||||||
case ImagePage():
|
case ImagePage():
|
||||||
with out.pair_tag("p", clazz="img-wrapper"):
|
with out.element("p", clazz="img-wrapper"):
|
||||||
for img in page.images:
|
for img in page.images:
|
||||||
# TODO: make a thing for this
|
# TODO: make a thing for this
|
||||||
out.empty_pair_tag(
|
out.empty_element(
|
||||||
"img",
|
"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:
|
if page.text is not None:
|
||||||
write_block(out, page.text)
|
write_block(out, page.text)
|
||||||
case CraftingMultiPage():
|
case CraftingMultiPage():
|
||||||
with out.pair_tag("blockquote", clazz="crafting-info"):
|
with out.element("blockquote", clazz="crafting-info"):
|
||||||
out.text(
|
out.text(
|
||||||
f"Depicted in the book: Several crafting recipes, for the "
|
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)
|
out.text(page.recipes[0].result.item)
|
||||||
for i in page.recipes[1:]:
|
for i in page.recipes[1:]:
|
||||||
out.text(", ")
|
out.text(", ")
|
||||||
with out.pair_tag("code"):
|
with out.element("code"):
|
||||||
out.text(i.result.item)
|
out.text(i.result.item)
|
||||||
out.text(".")
|
out.text(".")
|
||||||
if page.text is not None:
|
if page.text is not None:
|
||||||
write_block(out, page.text)
|
write_block(out, page.text)
|
||||||
case BrainsweepPage():
|
case BrainsweepPage():
|
||||||
with out.pair_tag("blockquote", clazz="crafting-info"):
|
with out.element("blockquote", clazz="crafting-info"):
|
||||||
out.text(
|
out.text(
|
||||||
f"Depicted in the book: A mind-flaying recipe producing the "
|
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(page.recipe.result.name)
|
||||||
out.text(".")
|
out.text(".")
|
||||||
if page.text is not None:
|
if page.text is not None:
|
||||||
write_block(out, page.text)
|
write_block(out, page.text)
|
||||||
case PageWithPattern():
|
case PageWithPattern():
|
||||||
with out.pair_tag("details", clazz="spell-collapsible"):
|
with out.element("details", clazz="spell-collapsible"):
|
||||||
out.empty_pair_tag("summary", clazz="collapse-spell")
|
out.empty_element("summary", clazz="collapse-spell")
|
||||||
for pattern in page.patterns:
|
for pattern in page.patterns:
|
||||||
with out.pair_tag(
|
with out.element(
|
||||||
"canvas",
|
"canvas",
|
||||||
clazz="spell-viz",
|
clazz="spell-viz",
|
||||||
width=216,
|
width=216,
|
||||||
|
@ -166,30 +161,30 @@ def write_page(out: Stream, pageid: str, page: Page[Any]):
|
||||||
)
|
)
|
||||||
write_block(out, page.text)
|
write_block(out, page.text)
|
||||||
case _:
|
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)}")
|
out.text(f"TODO: Missing processor for type: {type(page)}")
|
||||||
if isinstance(page, PageWithText):
|
if isinstance(page, PageWithText):
|
||||||
write_block(out, page.text)
|
write_block(out, page.text)
|
||||||
out.tag("br")
|
out.self_closing_element("br")
|
||||||
|
|
||||||
|
|
||||||
def write_entry(out: Stream, book: HexBook, entry: Entry):
|
def write_entry(out: HTMLStream, book: HexBook, entry: Entry):
|
||||||
with out.pair_tag("div", id=entry.id.path):
|
with out.element("div", id=entry.id.path):
|
||||||
with out.pair_tag_if(entry_spoilered(book, entry), "div", clazz="spoilered"):
|
with out.element_if(entry_spoilered(book, entry), "div", clazz="spoilered"):
|
||||||
with out.pair_tag("h3", clazz="entry-title page-header"):
|
with out.element("h3", clazz="entry-title page-header"):
|
||||||
write_block(out, entry.name)
|
write_block(out, entry.name)
|
||||||
anchor_toc(out)
|
anchor_toc(out)
|
||||||
permalink(out, entry.id.href)
|
permalink(out, entry.id.href)
|
||||||
for page in entry.pages:
|
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):
|
def write_category(out: HTMLStream, book: HexBook, category: Category):
|
||||||
with out.pair_tag("section", id=category.id.path):
|
with out.element("section", id=category.id.path):
|
||||||
with out.pair_tag_if(
|
with out.element_if(
|
||||||
category_spoilered(book, category), "div", clazz="spoilered"
|
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)
|
write_block(out, category.name)
|
||||||
anchor_toc(out)
|
anchor_toc(out)
|
||||||
permalink(out, category.id.href)
|
permalink(out, category.id.href)
|
||||||
|
@ -199,31 +194,31 @@ def write_category(out: Stream, book: HexBook, category: Category):
|
||||||
write_entry(out, book, entry)
|
write_entry(out, book, entry)
|
||||||
|
|
||||||
|
|
||||||
def write_toc(out: Stream, book: HexBook):
|
def write_toc(out: HTMLStream, book: HexBook):
|
||||||
with out.pair_tag("h2", id="table-of-contents", clazz="page-header"):
|
with out.element("h2", id="table-of-contents", clazz="page-header"):
|
||||||
out.text("Table of Contents")
|
out.text("Table of Contents")
|
||||||
with out.pair_tag(
|
with out.element(
|
||||||
"a",
|
"a",
|
||||||
href="javascript:void(0)",
|
href="javascript:void(0)",
|
||||||
clazz="permalink toggle-link small",
|
clazz="permalink toggle-link small",
|
||||||
data_target="toc-category",
|
data_target="toc-category",
|
||||||
title="Toggle all",
|
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")
|
permalink(out, "#table-of-contents")
|
||||||
for category in book.categories.values():
|
for category in book.categories.values():
|
||||||
with out.pair_tag("details", clazz="toc-category"):
|
with out.element("details", clazz="toc-category"):
|
||||||
with out.pair_tag("summary"):
|
with out.element("summary"):
|
||||||
with out.pair_tag(
|
with out.element(
|
||||||
"a",
|
"a",
|
||||||
href=category.id.href,
|
href=category.id.href,
|
||||||
clazz="spoilered" if category_spoilered(book, category) else "",
|
clazz="spoilered" if category_spoilered(book, category) else "",
|
||||||
):
|
):
|
||||||
out.text(category.name)
|
out.text(category.name)
|
||||||
with out.pair_tag("ul"):
|
with out.element("ul"):
|
||||||
for entry in category.entries:
|
for entry in category.entries:
|
||||||
with out.pair_tag("li"):
|
with out.element("li"):
|
||||||
with out.pair_tag(
|
with out.element(
|
||||||
"a",
|
"a",
|
||||||
href=entry.id.href,
|
href=entry.id.href,
|
||||||
clazz="spoilered" if entry_spoilered(book, entry) else "",
|
clazz="spoilered" if entry_spoilered(book, entry) else "",
|
||||||
|
@ -231,15 +226,15 @@ def write_toc(out: Stream, book: HexBook):
|
||||||
out.text(entry.name)
|
out.text(entry.name)
|
||||||
|
|
||||||
|
|
||||||
def write_book(out: Stream, book: HexBook):
|
def write_book(out: HTMLStream, book: HexBook):
|
||||||
with out.pair_tag("div", clazz="container"):
|
with out.element("div", clazz="container"):
|
||||||
with out.pair_tag("header", clazz="jumbotron"):
|
with out.element("header", clazz="jumbotron"):
|
||||||
with out.pair_tag("h1", clazz="book-title"):
|
with out.element("h1", clazz="book-title"):
|
||||||
write_block(out, book.name)
|
write_block(out, book.name)
|
||||||
write_block(out, book.landing_text)
|
write_block(out, book.landing_text)
|
||||||
with out.pair_tag("nav"):
|
with out.element("nav"):
|
||||||
write_toc(out, book)
|
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():
|
for category in book.categories.values():
|
||||||
write_category(out, book, category)
|
write_category(out, book, category)
|
||||||
|
|
||||||
|
@ -258,7 +253,7 @@ def generate_docs(book: HexBook, template: str) -> str:
|
||||||
_, *spoilers = line.split()
|
_, *spoilers = line.split()
|
||||||
book.context["spoilers"].update(spoilers)
|
book.context["spoilers"].update(spoilers)
|
||||||
elif line == "#DUMP_BODY_HERE\n":
|
elif line == "#DUMP_BODY_HERE\n":
|
||||||
write_book(Stream(output), book)
|
write_book(HTMLStream(output), book)
|
||||||
print("", file=output)
|
print("", file=output)
|
||||||
else:
|
else:
|
||||||
print(line, end="", file=output)
|
print(line, end="", file=output)
|
||||||
|
|
|
@ -17,7 +17,7 @@ from common.properties import Properties
|
||||||
from common.types import TryGetEnum
|
from common.types import TryGetEnum
|
||||||
from minecraft.i18n import I18nContext, LocalizedStr
|
from minecraft.i18n import I18nContext, LocalizedStr
|
||||||
|
|
||||||
from .tags import PairTag, Stream
|
from .html import HTMLElement, HTMLStream
|
||||||
|
|
||||||
DEFAULT_MACROS = {
|
DEFAULT_MACROS = {
|
||||||
"$(obf)": "$(k)",
|
"$(obf)": "$(k)",
|
||||||
|
@ -167,27 +167,27 @@ class Style(ABC, HexDocModel[Any], frozen=True):
|
||||||
raise ValueError(f"Unhandled style: {style_str}")
|
raise ValueError(f"Unhandled style: {style_str}")
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def tag(self, out: Stream) -> PairTag | nullcontext[None]:
|
def element(self, out: HTMLStream) -> HTMLElement | nullcontext[None]:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
class CommandStyle(Style, frozen=True):
|
class CommandStyle(Style, frozen=True):
|
||||||
type: CommandStyleType | BaseStyleType
|
type: CommandStyleType | BaseStyleType
|
||||||
|
|
||||||
def tag(self, out: Stream) -> PairTag | nullcontext[None]:
|
def element(self, out: HTMLStream) -> HTMLElement | nullcontext[None]:
|
||||||
match self.type:
|
match self.type:
|
||||||
case CommandStyleType.obfuscated:
|
case CommandStyleType.obfuscated:
|
||||||
return out.pair_tag("span", clazz="obfuscated")
|
return out.element("span", clazz="obfuscated")
|
||||||
case CommandStyleType.bold:
|
case CommandStyleType.bold:
|
||||||
return out.pair_tag("strong")
|
return out.element("strong")
|
||||||
case CommandStyleType.strikethrough:
|
case CommandStyleType.strikethrough:
|
||||||
return out.pair_tag("s")
|
return out.element("s")
|
||||||
case CommandStyleType.underline:
|
case CommandStyleType.underline:
|
||||||
return out.pair_tag("span", style="text-decoration: underline")
|
return out.element("span", style="text-decoration: underline")
|
||||||
case CommandStyleType.italic:
|
case CommandStyleType.italic:
|
||||||
return out.pair_tag("i")
|
return out.element("i")
|
||||||
case SpecialStyleType.base:
|
case SpecialStyleType.base:
|
||||||
return out.null_tag()
|
return nullcontext()
|
||||||
|
|
||||||
|
|
||||||
class ParagraphStyle(Style, frozen=True):
|
class ParagraphStyle(Style, frozen=True):
|
||||||
|
@ -212,8 +212,8 @@ class ParagraphStyle(Style, frozen=True):
|
||||||
def list_item(cls) -> Self:
|
def list_item(cls) -> Self:
|
||||||
return cls(attributes={"clazz": "fake-li"})
|
return cls(attributes={"clazz": "fake-li"})
|
||||||
|
|
||||||
def tag(self, out: Stream) -> PairTag:
|
def element(self, out: HTMLStream) -> HTMLElement:
|
||||||
return out.pair_tag("p", **self.attributes)
|
return out.element("p", **self.attributes)
|
||||||
|
|
||||||
|
|
||||||
def _format_href(value: str) -> str:
|
def _format_href(value: str) -> str:
|
||||||
|
@ -226,20 +226,20 @@ class FunctionStyle(Style, frozen=True):
|
||||||
type: FunctionStyleType | ColorStyleType
|
type: FunctionStyleType | ColorStyleType
|
||||||
value: str
|
value: str
|
||||||
|
|
||||||
def tag(self, out: Stream) -> PairTag:
|
def element(self, out: HTMLStream) -> HTMLElement:
|
||||||
match self.type:
|
match self.type:
|
||||||
case FunctionStyleType.link:
|
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:
|
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:
|
case FunctionStyleType.cmd_click:
|
||||||
return out.pair_tag(
|
return out.element(
|
||||||
"span",
|
"span",
|
||||||
clazz="has-cmd_click",
|
clazz="has-cmd_click",
|
||||||
title=f"When clicked, would execute: {self.value}",
|
title=f"When clicked, would execute: {self.value}",
|
||||||
)
|
)
|
||||||
case SpecialStyleType.color:
|
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
|
# intentionally not inheriting from Style, because this is basically an implementation
|
||||||
|
|
51
doc/src/patchouli/text/html.py
Normal file
51
doc/src/patchouli/text/html.py
Normal 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
|
|
@ -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
|
|
Loading…
Reference in a new issue