soul-browser/scripts/prepare-info-string.py
KaKi87 b5af18831f
Rebrand as Soul2 Browser (#34)
* Update README.md

* Rebrand as Soul2 Browser with branch-specific package IDs.

Use net.kaki87.soul2 on main and .testing elsewhere, slim Settings → Information to source/bug links plus README-extracted about text, and drop rate/share/feedback entries.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Use Soul2⁺ Browser as the display name for non-main builds.

Keep Soul2 Browser on main so stable and testing installs are easy to tell apart in the launcher.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Run the APK CI workflow on pushes to every branch.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Open Information links in the in-app web dialog.

MainUtil.I4 is for external-browser handoff and no-ops when Soul itself handles https, so App version / Source code / Bug tracker appeared dead.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Open Information paragraph hyperlinks in the in-app web dialog.

Replace Html URLSpans so README INFO links use SettingInfo.P0 like App version / Source code / Bug tracker.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Add a back chevron to the in-app Information web dialog.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-13 06:01:36 +02:00

124 lines
3.8 KiB
Python
Executable file

#!/usr/bin/env python3
"""Extract README INFO section and write it as an Android string resource (HTML)."""
from __future__ import annotations
import argparse
import html
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
README = ROOT / "README.md"
OUT_XML = ROOT / "app" / "res" / "values" / "soul2_info.xml"
BEGIN = "<!-- BEGIN INFO -->"
END = "<!-- END INFO -->"
def md_inline_to_html(text: str) -> str:
"""Convert a limited Markdown subset to HTML for TextView/Html.fromHtml."""
tokens: list[str] = []
def stash(html_fragment: str) -> str:
tokens.append(html_fragment)
return f"\x00T{len(tokens) - 1}\x00"
# Inline code
text = re.sub(
r"`([^`]+)`",
lambda m: stash(f"<tt>{html.escape(m.group(1))}</tt>"),
text,
)
# Links
text = re.sub(
r"\[([^\]]+)\]\(([^)]+)\)",
lambda m: stash(
f'<a href="{html.escape(m.group(2), quote=True)}">{html.escape(m.group(1))}</a>'
),
text,
)
# Bold
text = re.sub(
r"\*\*([^*]+)\*\*",
lambda m: stash(f"<b>{html.escape(m.group(1))}</b>"),
text,
)
text = re.sub(
r"__([^_]+)__",
lambda m: stash(f"<b>{html.escape(m.group(1))}</b>"),
text,
)
# Italic
text = re.sub(
r"(?<!\*)\*([^*]+)\*(?!\*)",
lambda m: stash(f"<i>{html.escape(m.group(1))}</i>"),
text,
)
text = re.sub(
r"(?<!_)_([^_]+)_(?!_)",
lambda m: stash(f"<i>{html.escape(m.group(1))}</i>"),
text,
)
# Escape remaining plain text, then restore stashed HTML.
text = html.escape(text)
text = re.sub(r"\x00T(\d+)\x00", lambda m: tokens[int(m.group(1))], text)
return text
def extract_info(readme_text: str) -> str:
begin_idx = readme_text.find(BEGIN)
end_idx = readme_text.find(END)
if begin_idx < 0 or end_idx < 0 or end_idx <= begin_idx:
raise ValueError("README.md missing <!-- BEGIN INFO --> / <!-- END INFO --> markers")
if begin_idx + len(BEGIN) >= len(readme_text) or readme_text[begin_idx + len(BEGIN)] != "\n":
raise ValueError("<!-- BEGIN INFO --> must be followed by a newline")
if end_idx == 0 or readme_text[end_idx - 1] != "\n":
raise ValueError("<!-- END INFO --> must be preceded by a newline")
body = readme_text[begin_idx + len(BEGIN) : end_idx].strip("\n")
paragraphs = [p.strip() for p in re.split(r"\n\s*\n", body) if p.strip()]
html_paras = []
for para in paragraphs:
flat = re.sub(r"\s*\n\s*", " ", para).strip()
html_paras.append(md_inline_to_html(flat))
return "<br/><br/>".join(html_paras)
def write_xml(html_body: str, out: Path) -> None:
# CDATA keeps real HTML tags for Html.fromHtml(getString(...)).
if "]]>" in html_body:
raise ValueError("INFO HTML must not contain ']]>'")
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(
'<?xml version="1.0" encoding="utf-8"?>\n'
"<resources>\n"
f' <string name="soul2_info_text"><![CDATA[{html_body}]]></string>\n'
"</resources>\n",
encoding="utf-8",
)
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--readme", type=Path, default=README)
parser.add_argument("--out", type=Path, default=OUT_XML)
args = parser.parse_args()
if not args.readme.exists():
print(f"error: missing {args.readme}", file=sys.stderr)
return 1
try:
html_body = extract_info(args.readme.read_text(encoding="utf-8"))
except ValueError as exc:
print(f"error: {exc}", file=sys.stderr)
return 1
write_xml(html_body, args.out)
print(f"Wrote {args.out} ({len(html_body)} chars of HTML)")
return 0
if __name__ == "__main__":
sys.exit(main())