mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-10 13:33:24 -05:00
fix: show path placeholders and hash lists on site pages
This commit is contained in:
1 parent
63af5167ed
commit
9ea6083b8a
4 files changed
+63
-20
No files matched your search
@@ -1,4 +1,4 @@
|
|||||||
emulator: SimCoupe
|
emulator: "SimCoupe (standalone)"
|
||||||
type: standalone
|
type: standalone
|
||||||
core_classification: official_port
|
core_classification: official_port
|
||||||
source: "https://github.com/simonowen/simcoupe"
|
source: "https://github.com/simonowen/simcoupe"
|
||||||
|
|||||||
+17
-18
@@ -1982,7 +1982,7 @@ def _render_emulator_file(
|
|||||||
lines.append("")
|
lines.append("")
|
||||||
lines.append(f"**`{fname}`** {badge_str}")
|
lines.append(f"**`{fname}`** {badge_str}")
|
||||||
if desc:
|
if desc:
|
||||||
lines.append(f"<br>{desc}")
|
lines.append(f"<br>{_escape_tags(str(desc))}")
|
||||||
lines.append("")
|
lines.append("")
|
||||||
|
|
||||||
details = []
|
details = []
|
||||||
@@ -2011,25 +2011,13 @@ def _render_emulator_file(
|
|||||||
bounds.append(f"max {_fmt_size(fmax)}")
|
bounds.append(f"max {_fmt_size(fmax)}")
|
||||||
details.append(f"Size: {', '.join(bounds)}")
|
details.append(f"Size: {', '.join(bounds)}")
|
||||||
if fsha1:
|
if fsha1:
|
||||||
s = fsha1[:12]
|
details.append(f"SHA1: {_hash_spans(fsha1)}")
|
||||||
details.append(
|
|
||||||
f'SHA1: <span class="rb-hash" title="{fsha1}">'
|
|
||||||
f"`{s}...`</span>"
|
|
||||||
)
|
|
||||||
if fmd5:
|
if fmd5:
|
||||||
s = fmd5[:12]
|
details.append(f"MD5: {_hash_spans(fmd5)}")
|
||||||
details.append(
|
|
||||||
f'MD5: <span class="rb-hash" title="{fmd5}">'
|
|
||||||
f"`{s}...`</span>"
|
|
||||||
)
|
|
||||||
if fcrc32:
|
if fcrc32:
|
||||||
details.append(f"CRC32: `{fcrc32}`")
|
details.append(f"CRC32: `{fcrc32}`")
|
||||||
if fsha256:
|
if fsha256:
|
||||||
s = fsha256[:12]
|
details.append(f"SHA256: {_hash_spans(fsha256)}")
|
||||||
details.append(
|
|
||||||
f'SHA256: <span class="rb-hash" title="{fsha256}">'
|
|
||||||
f"`{s}...`</span>"
|
|
||||||
)
|
|
||||||
if fadler32:
|
if fadler32:
|
||||||
details.append(f"Adler32: `{fadler32}`")
|
details.append(f"Adler32: `{fadler32}`")
|
||||||
if aliases:
|
if aliases:
|
||||||
@@ -2077,7 +2065,7 @@ def _render_emulator_file(
|
|||||||
for d in details:
|
for d in details:
|
||||||
lines.append(f"- {d}")
|
lines.append(f"- {d}")
|
||||||
if fnote:
|
if fnote:
|
||||||
lines.append(f"- {fnote}")
|
lines.append(f"- {_escape_tags(str(fnote))}")
|
||||||
if contents:
|
if contents:
|
||||||
lines.append(f"- Contents ({len(contents)} entries):")
|
lines.append(f"- Contents ({len(contents)} entries):")
|
||||||
for c in contents[:10]:
|
for c in contents[:10]:
|
||||||
@@ -2194,12 +2182,22 @@ def _render_structured_blocks(profile: dict) -> list[str]:
|
|||||||
continue
|
continue
|
||||||
lines.append(f'???+ abstract "{label}"')
|
lines.append(f'???+ abstract "{label}"')
|
||||||
lines.append("")
|
lines.append("")
|
||||||
|
start = len(lines)
|
||||||
_render_yaml_value(lines, val, indent=4)
|
_render_yaml_value(lines, val, indent=4)
|
||||||
|
lines[start:] = [_escape_tags(line) for line in lines[start:]]
|
||||||
lines.append("")
|
lines.append("")
|
||||||
|
|
||||||
return lines
|
return lines
|
||||||
|
|
||||||
|
|
||||||
|
def _hash_spans(value: str | list) -> str:
|
||||||
|
"""One abbreviated hash, or each of the hashes a profile accepts."""
|
||||||
|
values = [value] if isinstance(value, str) else [str(v) for v in value]
|
||||||
|
return ", ".join(
|
||||||
|
f'<span class="rb-hash" title="{v}">`{v[:12]}...`</span>' for v in values
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _availability_check(db: dict, data_names):
|
def _availability_check(db: dict, data_names):
|
||||||
"""A predicate answering whether the collection holds a file.
|
"""A predicate answering whether the collection holds a file.
|
||||||
|
|
||||||
@@ -2361,7 +2359,7 @@ def generate_emulator_page(
|
|||||||
[
|
[
|
||||||
"",
|
"",
|
||||||
'!!! info "Why no files"',
|
'!!! info "Why no files"',
|
||||||
f" {exclusion}",
|
f" {_escape_tags(str(exclusion))}",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
@@ -3972,6 +3970,7 @@ from siterender import ( # noqa: E402,F401
|
|||||||
_fmt_size,
|
_fmt_size,
|
||||||
_pct,
|
_pct,
|
||||||
_admonition_body,
|
_admonition_body,
|
||||||
|
_escape_tags,
|
||||||
_icon_name,
|
_icon_name,
|
||||||
_icon_url,
|
_icon_url,
|
||||||
system_icon_markdown,
|
system_icon_markdown,
|
||||||
|
|||||||
+25
-1
@@ -17,9 +17,33 @@ ICON_CACHE_PATH = Path(".cache") / "system_icons.json"
|
|||||||
# because a heading with a broken image reads worse than a heading without one.
|
# because a heading with a broken image reads worse than a heading without one.
|
||||||
_icon_available: dict[str, bool] = {}
|
_icon_available: dict[str, bool] = {}
|
||||||
|
|
||||||
|
_CODE_SPAN = re.compile(r"(`+).+?\1", re.S)
|
||||||
|
|
||||||
|
|
||||||
|
def _escape_tags(text: str) -> str:
|
||||||
|
"""Show a placeholder such as ``<system_dir>`` as it was written.
|
||||||
|
|
||||||
|
Profile prose names paths with angle brackets. Passed through as HTML
|
||||||
|
they are unknown tags and vanish from the page, and ``<title id>`` opens
|
||||||
|
a title element that swallows everything after it. Code spans keep their
|
||||||
|
brackets, and an autolink stays a link.
|
||||||
|
"""
|
||||||
|
def outside(chunk: str) -> str:
|
||||||
|
return re.sub(r"<(?!https?://|mailto:)", "<", chunk)
|
||||||
|
|
||||||
|
out: list[str] = []
|
||||||
|
position = 0
|
||||||
|
for match in _CODE_SPAN.finditer(text):
|
||||||
|
out.append(outside(text[position:match.start()]))
|
||||||
|
out.append(match.group(0))
|
||||||
|
position = match.end()
|
||||||
|
out.append(outside(text[position:]))
|
||||||
|
return "".join(out)
|
||||||
|
|
||||||
|
|
||||||
def _admonition_body(text: str) -> str:
|
def _admonition_body(text: str) -> str:
|
||||||
"""Indent prose without turning source tokens such as ``#if`` into H1s."""
|
"""Indent prose without turning source tokens such as ``#if`` into H1s."""
|
||||||
escaped = re.sub(r"(?m)^(\s*)#", r"\1\\#", text)
|
escaped = re.sub(r"(?m)^(\s*)#", r"\1\\#", _escape_tags(text))
|
||||||
return escaped.replace("\n", "\n ")
|
return escaped.replace("\n", "\n ")
|
||||||
|
|
||||||
def _icon_name(manufacturer: str, console_name: str) -> str:
|
def _icon_name(manufacturer: str, console_name: str) -> str:
|
||||||
|
|||||||
@@ -33,6 +33,26 @@ def _page(title: str, description: str, body: str = "") -> str:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class ProsePlaceholders(unittest.TestCase):
|
||||||
|
"""A path written with angle brackets reaches the reader as written."""
|
||||||
|
|
||||||
|
def test_a_placeholder_is_text_not_a_tag(self):
|
||||||
|
from siterender import _admonition_body, _escape_tags
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
_escape_tags("gameProfiles/<title id>.ini under <system_dir>/Cemu"),
|
||||||
|
"gameProfiles/<title id>.ini under <system_dir>/Cemu",
|
||||||
|
)
|
||||||
|
self.assertIn("<title id>", _admonition_body("read <title id>.ini"))
|
||||||
|
|
||||||
|
def test_code_spans_and_links_are_left_alone(self):
|
||||||
|
from siterender import _escape_tags
|
||||||
|
|
||||||
|
self.assertEqual(_escape_tags("`<system_dir>/x`"), "`<system_dir>/x`")
|
||||||
|
self.assertEqual(_escape_tags("see <https://example.org/a>"), "see <https://example.org/a>")
|
||||||
|
self.assertEqual(_escape_tags("size < 4096"), "size < 4096")
|
||||||
|
|
||||||
|
|
||||||
class RenderedSiteValidation(unittest.TestCase):
|
class RenderedSiteValidation(unittest.TestCase):
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
self.temp = tempfile.TemporaryDirectory(dir=TMP_ROOT)
|
self.temp = tempfile.TemporaryDirectory(dir=TMP_ROOT)
|
||||||
|
|||||||
Reference in new issue
Block a user