* 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>
124 lines
3.8 KiB
Python
Executable file
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())
|