diff --git a/install.ps1 b/install.ps1 index 4ab6b372f..dc3d0c50b 100644 --- a/install.ps1 +++ b/install.ps1 @@ -16,7 +16,7 @@ # and Invoke-WebRequest throws on a cut transfer before the hash is read: # the site's build rewrites this line there (scripts/sitebootstrap.py). $mismatchAdvice = "The download was most likely cut short: try again. Within minutes of an update of this project the two files can come from caches of different ages. Waiting five minutes settles that." - $defaultInstallSha256 = "61176387d38c67e596c1669fee2bb3b981333ab0b808dd2e280a5a3870957738" + $defaultInstallSha256 = "e9bfce57f1aab1123937498d7ed7913223762df755bb572b9a36f2d6a69b7a55" # The pin above refuses any changed byte: this cap only stops a body that # cannot be the installer (a sign-in portal's page) before it is hashed. $maximumInstallerBytes = 8MB diff --git a/install.py b/install.py index 13d713275..5dab26d55 100644 --- a/install.py +++ b/install.py @@ -4423,9 +4423,9 @@ def fetch_manifest(plat: str) -> dict: # request, so the revision is what has no such list. print(f" No file list for {plat} at {RELEASE_REF}.", file=sys.stderr) if RELEASE_REF != DEFAULT_RELEASE_REF: - print(f" RETROBIOS_REF={RELEASE_REF} names a tag that does not exist or " - f"predates {plat}: unset RETROBIOS_REF, or pick a tag from " - "https://github.com/Abdess/retrobios/releases.", file=sys.stderr) + print(f" RETROBIOS_REF={RELEASE_REF} has no list for {plat}: unset it, or " + "pick a newer tag from https://github.com/Abdess/retrobios/releases.", + file=sys.stderr) else: print(" The repository may be moving its files: run the same command " "again in a few minutes.", file=sys.stderr) @@ -4441,9 +4441,9 @@ def fetch_manifest(plat: str) -> dict: message=words if code != "network" else f"the file list could not be fetched: {words}") if code == "offline": - print(f" No file list for {plat}: {offline_words()}. Give it with " - f"--manifest FILE (a copy of install/{plat}.json) or " - f"--from-pack PACK, or run {offline_undo()}.", file=sys.stderr) + print(f" No file list for {plat}: {offline_words()}. Give --manifest FILE " + f"(a copy of install/{plat}.json) or --from-pack PACK, or run " + f"{offline_undo()}.", file=sys.stderr) sys.exit(1) if code != "network": print(f" Could not fetch the file list for {plat}.", file=sys.stderr) @@ -4502,9 +4502,9 @@ def fetch_targets(plat: str) -> "dict | None": if LOCAL.offline: source = (f"{LOCAL.pack.name} carries none" if LOCAL.pack is not None else f"{offline_named()} makes no request") - print(f"Error: --target reads the cores of each {display_name(plat)} board from a " - f"list, and {source}. Put install/targets/{plat}.json beside the file list " - "named with --manifest, or run with the network.", file=sys.stderr) + print(f"Error: no {display_name(plat)} board list here ({source}). Put " + f"install/targets/{plat}.json beside the --manifest file, or go online.", + file=sys.stderr) sys.exit(2) url = TARGETS_URL.format(platform=plat) try: @@ -14011,9 +14011,8 @@ def main() -> None: and not (named and named not in TARGET_PLATFORMS)): # No list can be at hand: a pack carries one, and so can the folder # of a --manifest (targets/.json). - parser.error("--target and --list-targets read a core list from the network, " - f"which {offline_named()} does not use: name a pack with --from-pack, " - "or a file list with --manifest and targets/.json beside it") + parser.error(f"--target needs a core list, which {offline_named()} does not fetch: " + "give --from-pack, or --manifest with targets/.json beside it") install_bounded_opener() if getattr(args, "from") and ( args.list_platforms or args.list_targets or args.list_systems @@ -15207,8 +15206,7 @@ def card_install_dest(named: str, plat: str, interactive: bool, ask: bool = True message=f"{system} keeps its BIOS files on its SD card, and no " "card was named with --dest") print(f" {system} keeps its BIOS files on its SD card, not on this machine. " - f"Mount the card and name its root with --dest, for example: {example}", - file=sys.stderr) + f"Mount it and give its root with --dest: {example}", file=sys.stderr) sys.exit(1) print(f" {system} keeps its BIOS files {card_where(system, setup.below)}, not on " "this machine. " @@ -15393,9 +15391,8 @@ def _install(args: argparse.Namespace, real_stdout: TextIO) -> None: kind = _output_kind(real_stdout) where = f"Output goes to a {kind}" if kind else "Output is not a terminal" undo = "the pipe" if kind == "pipe" else "the redirection" - print(f"{where}, so nothing is asked: the run goes on as with --no-input. " - f"Add --no-input to say so, or run it without {undo} to answer.", - file=sys.stderr) + print(f"{where}, so nothing is asked. Add --no-input to say so, or run it " + f"without {undo} to answer.", file=sys.stderr) OUTPUT.verbose = bool(getattr(args, "verbose", False)) OUTPUT.where_said = False detail(f"{term.bold('RetroBIOS')} {term.dim(installer_id())}\n") diff --git a/install.sh b/install.sh index c8bdfb3e8..92e49ff45 100755 --- a/install.sh +++ b/install.sh @@ -23,7 +23,7 @@ DEFAULT_INSTALL_URL="https://raw.githubusercontent.com/Abdess/retrobios/main/ins # curl or wget stop on a cut transfer before the hash is read: the site's # build rewrites this line there (scripts/sitebootstrap.py). MISMATCH_ADVICE="The download was most likely cut short: try again. Within minutes of an update of this project the two files can come from caches of different ages. Waiting five minutes settles that." -DEFAULT_INSTALL_SHA256="61176387d38c67e596c1669fee2bb3b981333ab0b808dd2e280a5a3870957738" +DEFAULT_INSTALL_SHA256="e9bfce57f1aab1123937498d7ed7913223762df755bb572b9a36f2d6a69b7a55" # The pin above refuses any changed byte: this cap only stops a body that # cannot be the installer (a sign-in portal's page) before it is hashed. MAX_INSTALLER_BYTES=8388608 diff --git a/install/launchers/index.json b/install/launchers/index.json index 2bd3d1a2b..5f955c7bf 100644 --- a/install/launchers/index.json +++ b/install/launchers/index.json @@ -27,8 +27,8 @@ { "dest": "roms/ports/retrobios/install.py", "repo_path": "install.py", - "size": 1143258, - "sha256": "61176387d38c67e596c1669fee2bb3b981333ab0b808dd2e280a5a3870957738", + "size": 1142976, + "sha256": "e9bfce57f1aab1123937498d7ed7913223762df755bb572b9a36f2d6a69b7a55", "executable": false }, { @@ -71,8 +71,8 @@ "muos": { "archive": { "dest": "ARCHIVE/RetroBIOS.muxapp", - "size": 1196883, - "sha256": "2c84115b87ed4bcd4fdf4d64d56fc1221485cbd9822cfeb0770b07b9b4226e51" + "size": 1196601, + "sha256": "9305dd087110937f9c4c2d08cf4b52aa55dc0c8835109991f913e23ca30f3926" }, "members": [ { @@ -92,8 +92,8 @@ { "name": "RetroBIOS/install.py", "repo_path": "install.py", - "size": 1143258, - "sha256": "61176387d38c67e596c1669fee2bb3b981333ab0b808dd2e280a5a3870957738", + "size": 1142976, + "sha256": "e9bfce57f1aab1123937498d7ed7913223762df755bb572b9a36f2d6a69b7a55", "executable": false }, { @@ -166,8 +166,8 @@ { "dest": ".config/modules/retrobios/install.py", "repo_path": "install.py", - "size": 1143258, - "sha256": "61176387d38c67e596c1669fee2bb3b981333ab0b808dd2e280a5a3870957738", + "size": 1142976, + "sha256": "e9bfce57f1aab1123937498d7ed7913223762df755bb572b9a36f2d6a69b7a55", "executable": false }, { diff --git a/tests/test_install.py b/tests/test_install.py index cd75993d7..a6d974726 100644 --- a/tests/test_install.py +++ b/tests/test_install.py @@ -7677,5 +7677,34 @@ class APackageHintNamesThePackageCommand(unittest.TestCase): "uvx retrobios --platform retroarch --dest DIR")) +class AnErrorIsOneShortSentence(unittest.TestCase): + """A message on stderr says what to do in a few words: one of 35 words + asked a beginner to read a paragraph to find the option to give.""" + + BUDGET = 25 + + def test_every_stderr_message_is_short(self): + import ast + + tree = ast.parse((REPO_ROOT / "install.py").read_text(encoding="utf-8")) + long = [] + for node in ast.walk(tree): + if not isinstance(node, ast.Call): + continue + name = getattr(node.func, "id", getattr(node.func, "attr", "")) + to_err = any(k.arg == "file" and "stderr" in ast.unparse(k.value) + for k in node.keywords) + if not (to_err or name == "error"): + continue + text = " ".join(ast.unparse(a) for a in node.args) + if ".command(" in text: + continue + prose = re.sub(r"\{[^}]*\}", "X", text) + words = len(re.findall(r"[A-Za-z']+", prose)) + if words > self.BUDGET: + long.append((node.lineno, words)) + self.assertEqual(long, []) + + if __name__ == "__main__": unittest.main()