mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-11-01 07:33:45 -06:00
fix: one total and the same words in both modes
Commands break between shell words with a continuation the shell reads, an option kept with its value. The 40x12 setup and plan keep a note or a folder whole. Line mode counts files in place and missing, and names the BIOS folder, as the screen does. The systems question fits its terminal.
This commit is contained in:
1 parent
5bf29b3dab
commit
23804a8da3
31 files changed
+558
-186
No files matched your search
+1
-1
@@ -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 = "48a5fca25303757fc2874262c1d176992bd7340da5c59f58c6e911383bf54817"
|
||||
$defaultInstallSha256 = "67cbe64d1de7b2f38edd25cab1b34384c55cac77e13f22f4c302fa2b04a809d7"
|
||||
$maximumInstallerBytes = 2MB
|
||||
$installerExit = 0
|
||||
$installer = if ($PSScriptRoot) { Join-Path $PSScriptRoot "install.py" } else { $null }
|
||||
|
||||
+221
-36
@@ -5999,7 +5999,7 @@ def _report_outcome(
|
||||
kept = _kept_lines() + ([
|
||||
f"{_plural(len(unplaced), 'file')} read from "
|
||||
f"{unplaced[0].get('frontend') or 'the frontend'}'s save directory left out: "
|
||||
"its place is not known. Name it with --save-dir next time."] if unplaced else [])
|
||||
f"its place is not known. {LINE_SAVE_REMEDY}"] if unplaced else [])
|
||||
kept += list(notes or [])
|
||||
if interrupted:
|
||||
say(f"\nStopped. {_plural(downloaded, 'file')} installed.")
|
||||
@@ -6068,7 +6068,7 @@ def _report_outcome(
|
||||
# Under the files it is about: after the save-directory line, "They"
|
||||
# read as that one file.
|
||||
say(f" {remedy}")
|
||||
say(f"Location: {_locations(paths)}")
|
||||
say(f"BIOS folder: {_locations(paths)}")
|
||||
for line in kept + advice:
|
||||
say(line)
|
||||
sys.exit(1 if downloaded else 2)
|
||||
@@ -6079,9 +6079,12 @@ def _report_outcome(
|
||||
for _d, r in failures):
|
||||
# The folder was never the problem: naming it as the place being
|
||||
# written to sent people looking at their disk.
|
||||
say(f"Destination: {_locations(paths)}")
|
||||
say(f"BIOS folder: {_locations(paths)}")
|
||||
elif _retry_words(failures):
|
||||
# Nothing was written: every file is one the pack lacks.
|
||||
say(f"BIOS folder: {_locations(paths)}")
|
||||
else:
|
||||
say(f"Location that was being written to: {_locations(paths)}")
|
||||
say(f"BIOS folder that was being written to: {_locations(paths)}")
|
||||
for line in kept + advice:
|
||||
say(line)
|
||||
say(_retry_words(failures) or "Run the same command again once that is resolved. "
|
||||
@@ -6095,7 +6098,7 @@ def _report_outcome(
|
||||
say(f" {sources}")
|
||||
if installed_kinds:
|
||||
say(installed_kinds)
|
||||
say(f"Location: {_locations(paths)}")
|
||||
say(f"BIOS folder: {_locations(paths)}")
|
||||
for line in kept + advice:
|
||||
say(line)
|
||||
retry = _retry_words(failures)
|
||||
@@ -6105,13 +6108,14 @@ def _report_outcome(
|
||||
say("Run the same command again to retry only those.")
|
||||
sys.exit(1)
|
||||
|
||||
say(f"\nDone. {_plural(downloaded, 'file')} installed, "
|
||||
f"{_plural(up_to_date, 'file')} already up to date.")
|
||||
# No zero count: "0 files already up to date" said nothing.
|
||||
say(f"\nDone. {_plural(downloaded, 'file')} installed"
|
||||
+ (f", {_plural(up_to_date, 'file')} already up to date." if up_to_date else "."))
|
||||
if sources:
|
||||
say(f" {sources}")
|
||||
if installed_kinds:
|
||||
say(installed_kinds)
|
||||
say(f"Location: {_locations(paths)}")
|
||||
say(f"BIOS folder: {_locations(paths)}")
|
||||
for line in kept:
|
||||
say(line)
|
||||
if omitted > len(unplaced):
|
||||
@@ -7653,11 +7657,20 @@ def _pick(
|
||||
print(f"\n{title}")
|
||||
width = len(str(len(rows)))
|
||||
shown = {name: screen_text((labels or {}).get(name, name)) for name, _files in rows}
|
||||
# The sizes in one column, whatever the longest label: cut at 40, the
|
||||
# longer names pushed theirs right (tui_audit_13 P3-13).
|
||||
name_width = max(cell_width(label) for label in shown.values())
|
||||
for index, (name, files) in enumerate(rows, 1):
|
||||
print(f" {index:>{width}}) {_pad(shown[name], name_width)} {sizer(files)}")
|
||||
sizes = {name: sizer(files) for name, files in rows}
|
||||
# The sizes in one column, as wide as the terminal leaves the names: the
|
||||
# longest label set it for every row, and 53 of 66 rows were folded at
|
||||
# 80 columns (code_audit_21 P3-9). A longer label takes its own line.
|
||||
lead = 2 + width + 2
|
||||
room = (shutil.get_terminal_size((80, 24)).columns - 1 - lead - 2
|
||||
- max(cell_width(size) for size in sizes.values()))
|
||||
name_width = min(max(cell_width(label) for label in shown.values()), max(16, room))
|
||||
for index, (name, _files) in enumerate(rows, 1):
|
||||
if cell_width(shown[name]) > name_width:
|
||||
print(f" {index:>{width}}) {shown[name]}")
|
||||
print(f"{' ' * (lead + name_width)} {sizes[name]}")
|
||||
continue
|
||||
print(f" {index:>{width}}) {_pad(shown[name], name_width)} {sizes[name]}")
|
||||
if note:
|
||||
print(f"\n{note}")
|
||||
hint = "order of preference, e.g. 2,1" if ordered else "e.g. 1,3-5"
|
||||
@@ -12259,9 +12272,13 @@ def _install(args: argparse.Namespace, real_stdout: TextIO) -> None:
|
||||
hint = Invocation()
|
||||
hint.os_type, hint.options = os_type, _INVOCATION.options
|
||||
example = _default_dest(os_type, args.platform)
|
||||
# The run's own way of answering, kept: a script copying the
|
||||
# example stopped at a question (tui_audit_14 P3-12).
|
||||
answered = "".join(f" {flag}" for flag, on in (
|
||||
("--yes", args.yes), ("--no-input", args.no_input)) if on)
|
||||
print(" Name its folder with --dest, for example:\n "
|
||||
+ hint.command(args.platform, example if example is not None
|
||||
else "<its BIOS folder>"), file=sys.stderr)
|
||||
else "<its BIOS folder>") + answered, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
elif args.platform in PORTABLE_ONLY:
|
||||
# A portable emulator reads the folder beside its executable:
|
||||
@@ -12805,10 +12822,17 @@ def _install(args: argparse.Namespace, real_stdout: TextIO) -> None:
|
||||
files, bios_path, jobs=args.jobs, term=term
|
||||
)
|
||||
present = len(up_to_date) + len(mismatched)
|
||||
print(
|
||||
f" {present}/{run_counts.files} present "
|
||||
f"({len(up_to_date)} verified, {len(mismatched)} wrong hash)"
|
||||
)
|
||||
# The screen's words: "0/22 present (0 verified, 0 wrong hash)"
|
||||
# beside its "0 in place, 22 missing, 0 wrong copies" (tui_audit_14
|
||||
# P3-10). The data files the head counts are among them, as the
|
||||
# screen counts them (tui_audit_14 P3-7).
|
||||
wrong = len(mismatched)
|
||||
trees_missing = sum(len(data_directory_state(entry, bios_path)[1])
|
||||
for entry in data_dirs)
|
||||
trees_held = head_data - trees_missing
|
||||
print(f" {len(up_to_date) + trees_held} in place, "
|
||||
f"{run_counts.files - present + trees_missing} missing, "
|
||||
f"{wrong} wrong {'copy' if wrong == 1 else 'copies'}")
|
||||
|
||||
# A partial copy left by an interrupted run is useless once the file
|
||||
# is in place: it goes.
|
||||
@@ -13587,6 +13611,94 @@ def _glued_words(text: str) -> list[str]:
|
||||
_SHELL_WORD = re.compile(r"""(?:'[^']*'|"[^"]*"|[^\s'"]+|['"])+""")
|
||||
|
||||
|
||||
def _path_tail(text: str, width: int) -> str:
|
||||
"""A path cut to its last folders that fit after ".../", as a person
|
||||
reads it on a narrow screen (".../retroarch/system"), a word that is no
|
||||
path cut in its middle."""
|
||||
if cell_width(text) <= width:
|
||||
return text
|
||||
separator = "\\" if "\\" in text and "/" not in text else "/"
|
||||
parts = text.rstrip(separator).split(separator)
|
||||
tail = ""
|
||||
for part in reversed(parts):
|
||||
longer = f"{part}{separator}{tail}" if tail else part
|
||||
if cell_width(f"...{separator}{longer}") > width:
|
||||
break
|
||||
tail = longer
|
||||
return f"...{separator}{tail}" if tail else middle(text, width)
|
||||
|
||||
|
||||
def _command_groups(text: str) -> list[str]:
|
||||
"""A command's shell words as they stay together on a line: a long
|
||||
option with its value. "--platform" ended a line, its value on the next
|
||||
(tui_audit_14 P3-8)."""
|
||||
groups: list[str] = []
|
||||
for word in _SHELL_WORD.findall(text):
|
||||
last = groups[-1] if groups else ""
|
||||
if (last.startswith("--") and len(last) > 2 and " " not in last and "=" not in last
|
||||
and not word.startswith("-") and word != "|"):
|
||||
groups[-1] = f"{last} {word}"
|
||||
else:
|
||||
groups.append(word)
|
||||
return groups
|
||||
|
||||
|
||||
def _continuation() -> str:
|
||||
"""The character that ends a line of a command the shell reads on: a
|
||||
backtick in PowerShell, a backslash in sh."""
|
||||
return "`" if os.environ.get("RETROBIOS_BOOTSTRAP") == "ps1" or os.name == "nt" else "\\"
|
||||
|
||||
|
||||
def _command_wrap(text: str, width: int, indent: str = " ") -> list[str]:
|
||||
"""A command too wide for width, broken between its shell words, each
|
||||
line but the last ending with the continuation the shell accepts, so a
|
||||
copy of the lines runs as the command does. A line one group wide that
|
||||
fits only without the indent loses it. A path still wider keeps its
|
||||
last folders (_path_tail), said by TUI_SHORTENED: folded by the
|
||||
terminal, it read as broken inside a word (tui_audit_14 P3-8). A web
|
||||
address is never cut."""
|
||||
lead = text[:len(text) - len(text.lstrip(" "))]
|
||||
tail = " " + _continuation()
|
||||
groups = _command_groups(text)
|
||||
lines: list[str] = []
|
||||
line = ""
|
||||
|
||||
def close(done: str, last: bool) -> str:
|
||||
end = "" if last else tail
|
||||
prefix = lead + indent if lines else lead
|
||||
for fewer in (lead, ""):
|
||||
if cell_width(prefix + done + end) > width >= cell_width(fewer + done + end):
|
||||
prefix = fewer
|
||||
return prefix + done + end
|
||||
|
||||
for number, group in enumerate(groups):
|
||||
end = 0 if number == len(groups) - 1 else len(tail)
|
||||
room = width - cell_width(lead + indent) - end
|
||||
# A group is an option and its value: split at the first space, a
|
||||
# quoted value keeping its own.
|
||||
option, _space, value = (group.partition(" ") if group.startswith("--") and " " in group
|
||||
else ("", "", group))
|
||||
if cell_width(group) > room and "://" not in value and _is_path_word(value.strip("'\"")):
|
||||
keep = cell_width(option) + 1 if option else 0
|
||||
quote = value[0] if value[:1] in "'\"" and value[-1:] == value[:1] else ""
|
||||
bare = value[1:-1] if quote else value
|
||||
group = (f"{option} " if option else "") + quote + _path_tail(
|
||||
bare, max(8, room - keep - 2 * len(quote))) + quote
|
||||
prefix = lead + indent if lines else lead
|
||||
longer = f"{line} {group}" if line else group
|
||||
if not line or cell_width(prefix + longer + (tail if end else "")) <= width:
|
||||
line = longer
|
||||
continue
|
||||
lines.append(close(line, False))
|
||||
line = group
|
||||
lines.append(close(line, True))
|
||||
return lines
|
||||
|
||||
|
||||
# Said under a command a path of which was cut to fit (_command_wrap).
|
||||
TUI_SHORTENED = "Shortened to fit this window."
|
||||
|
||||
|
||||
def _wrap(text: str, width: int, indent: str = " ", whole: str = "") -> list[str]:
|
||||
"""Lines of at most width cells, the ones after the first indented.
|
||||
|
||||
@@ -13597,10 +13709,12 @@ def _wrap(text: str, width: int, indent: str = " ", whole: str = "") -> list[st
|
||||
and a backup folder's dated name fell on a line of its own."""
|
||||
if cell_width(text) <= width:
|
||||
return [text]
|
||||
if whole == "command":
|
||||
return _command_wrap(text, width, indent)
|
||||
lead = text[:len(text) - len(text.lstrip(" "))]
|
||||
lines: list[str] = []
|
||||
line = ""
|
||||
words = (_SHELL_WORD.findall(text) if whole == "command" else _glued_words(text))
|
||||
words = _glued_words(text)
|
||||
|
||||
def close(done: str) -> str:
|
||||
# A line one word wide that fits without the indent loses it: one
|
||||
@@ -13636,8 +13750,12 @@ def _wrap(text: str, width: int, indent: str = " ", whole: str = "") -> list[st
|
||||
# The rest of a word broken at a slash joins it without a space:
|
||||
# a path was drawn "retroarch/ system", and copied wrong.
|
||||
longer = f"{line}{'' if index else ' '}{piece}" if line else piece
|
||||
# A path's pieces keep the indent, which they are cut to fit:
|
||||
# joined past it, the last piece of a folder lost its indent
|
||||
# (tui_audit_14 P3-9).
|
||||
if not line or cell_width(prefix + longer) <= width or (
|
||||
lines and " " not in longer and cell_width(lead + longer) <= width):
|
||||
lines and " " not in longer and cell_width(lead + longer) <= width
|
||||
and not _is_path_word(word)):
|
||||
line = longer
|
||||
else:
|
||||
lines.append(close(line))
|
||||
@@ -17294,7 +17412,16 @@ def _text_lines(items: list, width: int, whole: bool = False) -> list[TuiLine]:
|
||||
command = style == "command" or getattr(item, "command", False)
|
||||
kind = "command" if command else "text" if whole else ""
|
||||
folder = str(item).lstrip(" ").startswith(_FOLDER_LABEL)
|
||||
for number, piece in enumerate(_wrap(str(item), width, whole=kind)):
|
||||
# A folder on its own line wraps at its own indent.
|
||||
pieces = _wrap(str(item), width, "" if style == "path" and str(item).startswith(" ")
|
||||
else " ", whole=kind)
|
||||
if command and "..." in "".join(pieces) and "..." not in str(item):
|
||||
# Its path cut to fit: said under it, never as part of it.
|
||||
pieces.append(TUI_SHORTENED)
|
||||
for number, piece in enumerate(pieces):
|
||||
if piece is TUI_SHORTENED:
|
||||
lines.append(_styled(fit(piece, width), "dim"))
|
||||
continue
|
||||
if cell_width(piece) <= width:
|
||||
# The rest of a BIOS folder's line keeps its weight.
|
||||
line = _styled(fit(piece, width), "folder" if folder and number
|
||||
@@ -17395,8 +17522,15 @@ def _compose(
|
||||
budget -= len(top)
|
||||
gap = [_styled("")] if top and row_lines and budget > 0 else []
|
||||
budget -= len(gap)
|
||||
tail = _text_lines(list(after), width)
|
||||
bottom = ([_styled("")] + tail)[:budget] if tail and budget > 1 else []
|
||||
# Whole items only: cut to the room left, a note ended in the
|
||||
# middle of its sentence at 40x12 (tui_audit_14 P3-5).
|
||||
tail: list[TuiLine] = []
|
||||
for item in after:
|
||||
lines = _text_lines([item], width)
|
||||
if 1 + len(tail) + len(lines) > budget:
|
||||
break
|
||||
tail += lines
|
||||
bottom = [_styled(""), *tail] if tail else []
|
||||
body = top + gap + row_lines + bottom
|
||||
body += [_styled("")] * (room - len(body))
|
||||
return body + footer_lines
|
||||
@@ -17755,9 +17889,11 @@ def _saves_lines(state: TuiState, saves: dict | None, whole: bool = False) -> li
|
||||
words, folder = save_where_words(saves["folder"])
|
||||
# The folder alone is cut in the middle: cut with the sentence, it
|
||||
# lost "directory goes to" even at 80 columns.
|
||||
# The folder on its own line at the BIOS folder's indent: at the
|
||||
# label's, the two folders of a plan sat at two (tui_audit_14 P3-9).
|
||||
return [f"{_plural(count, 'file')} read from {name}'s save directory "
|
||||
f"{'goes' if count == 1 else 'go'} to{' ' + words if words else ''}:",
|
||||
shown_path(folder) if whole else _short_path(Path(folder), state.home)]
|
||||
" " + (shown_path(folder) if whole else _short_path(Path(folder), state.home))]
|
||||
|
||||
|
||||
def _unplaced_lines(summary: dict | None, state: TuiState,
|
||||
@@ -17773,6 +17909,13 @@ def _unplaced_lines(summary: dict | None, state: TuiState,
|
||||
f"its place is not known. {remedy or TUI_SAVE_REMEDY}"]
|
||||
|
||||
|
||||
# Said when ES-DE is found, and in fewer words where the long form would
|
||||
# give way: at 40x12 it ended in the middle of its sentence (tui_audit_14
|
||||
# P3-5).
|
||||
TUI_ESDE_NOTE = "ES-DE found: its BIOS files go to each emulator, not to ES-DE itself."
|
||||
TUI_ESDE_SHORT = "ES-DE: files go to each emulator."
|
||||
|
||||
|
||||
def _view_setup(state: TuiState, width: int, height: int) -> tuple:
|
||||
rows = tui_rows(state)
|
||||
focus = rows[min(state.focus.get("setup", 0), len(rows) - 1)]
|
||||
@@ -17782,10 +17925,15 @@ def _view_setup(state: TuiState, width: int, height: int) -> tuple:
|
||||
else "A drive here could be either of these:" if state.doubts
|
||||
else "Nothing was found here."]
|
||||
status = [state.status] if state.status else []
|
||||
return ("Where do you play?",
|
||||
_compose(state, width, height, before, rows, after + status + state.notes,
|
||||
pinned=state.refused),
|
||||
("A/Enter Continue", "B/Esc Quit", "? Help"))
|
||||
body = _compose(state, width, height, before, rows, after + status + state.notes,
|
||||
pinned=state.refused)
|
||||
if TUI_ESDE_NOTE in state.notes and not any(
|
||||
str(line).strip().startswith("ES-DE found") for line in body):
|
||||
# The note gave way whole: its short form, where it fits.
|
||||
notes = [TUI_ESDE_SHORT if note == TUI_ESDE_NOTE else note for note in state.notes]
|
||||
body = _compose(state, width, height, before, rows, after + status + notes,
|
||||
pinned=state.refused)
|
||||
return ("Where do you play?", body, ("A/Enter Continue", "B/Esc Quit", "? Help"))
|
||||
|
||||
|
||||
def _view_platforms(state: TuiState, width: int, height: int) -> tuple:
|
||||
@@ -18102,7 +18250,8 @@ def _view_plan(state: TuiState, width: int, height: int) -> tuple:
|
||||
# one way on the plan and another on the result (tui_audit_13 P3-3).
|
||||
lines = [piece for line in lines for piece in (
|
||||
[_styled(part, "folder" if index and str(line).startswith(_FOLDER_LABEL)
|
||||
else "") for index, part in enumerate(_wrap(str(line), width))]
|
||||
else "") for index, part in enumerate(_wrap(
|
||||
str(line), width, "" if str(line).startswith(" ") else " "))]
|
||||
if getattr(line, "style", "") == "path" and cell_width(str(line)) > width
|
||||
else [line])]
|
||||
rows = tui_rows(state)
|
||||
@@ -18127,10 +18276,38 @@ def _view_plan(state: TuiState, width: int, height: int) -> tuple:
|
||||
system = detected_os(state.platform, state.dest) if state.dest is not None else ""
|
||||
name = display_name(state.platform, state.manifest)
|
||||
return (f"{system} ({name} files)" if system and system != name else name,
|
||||
_compose(state, width, height, lines, rows),
|
||||
_plan_body(state, width, height, lines, rows),
|
||||
("A/Enter Install", "B/Esc Back", "Y/o Options", "? Help"))
|
||||
|
||||
|
||||
def _plan_body(state: TuiState, width: int, height: int, lines: list,
|
||||
rows: list[TuiRow]) -> list[TuiLine]:
|
||||
"""The plan's text and rows. Where both do not fit, the headline, the
|
||||
whole BIOS folder and the version note stay and the rows scroll under
|
||||
them: at 40x12 the plan named ~/.var/app/ as the folder, and dropped the
|
||||
note that the list is for another version (tui_audit_14 P3-6)."""
|
||||
body = _compose(state, width, height, lines, rows)
|
||||
at = next((index for index, line in enumerate(lines)
|
||||
if str(line).startswith(_FOLDER_LABEL.rstrip())), None)
|
||||
kept = list(lines[:1])
|
||||
if at is not None:
|
||||
# The folder line and the rest of the folder wrapped after it.
|
||||
end = at + 1
|
||||
while end < len(lines) and getattr(lines[end], "style", "") == "folder":
|
||||
end += 1
|
||||
kept += lines[at:end]
|
||||
kept += [line for line in lines if state.version and str(line) == state.version]
|
||||
top = _text_lines(kept, width)
|
||||
drawn = [str(line).strip() for line in body]
|
||||
# Three lines at least for the rows: two of them and where the list is,
|
||||
# the blank line above them given up first.
|
||||
if (all(str(line).strip() in drawn for line in top)
|
||||
or len(top) + min(len(rows), 3) > height):
|
||||
return body
|
||||
gap = [_styled("")] if len(top) + 1 + min(len(rows), 3) <= height else []
|
||||
return top + gap + _compose(state, width, height - len(top) - len(gap), [], rows)
|
||||
|
||||
|
||||
def _plan_data(state: TuiState, check: dict) -> tuple[int, int | None]:
|
||||
"""The data files the plan counts and their size: the check's, or,
|
||||
while the plan estimates the systems ticked, those of the data folders
|
||||
@@ -18598,6 +18775,11 @@ def tui_outcome(state: TuiState) -> tuple[str, list[str]]:
|
||||
# twice, the remedy between them (tui_audit_11 P3-18).
|
||||
lines[0] = sources
|
||||
sources = ""
|
||||
elif title == "Done" and sources and "," not in sources and not summary.get("kept"):
|
||||
# Every file from one place: "22 files installed." over "22 files
|
||||
# copied from packx." said one count twice (tui_audit_14 P3-10).
|
||||
lines[0] = sources + head[head.index(".") + 1:]
|
||||
sources = ""
|
||||
if sources:
|
||||
lines.append(sources)
|
||||
if left:
|
||||
@@ -18697,13 +18879,16 @@ def tui_outcome(state: TuiState) -> tuple[str, list[str]]:
|
||||
if state.mouse and typed:
|
||||
# A drag over the command selected nothing: the terminal sends the
|
||||
# press as a report. Said where it is read (tui_audit_13 P3-10).
|
||||
lines.append(_styled(TUI_RESULT_COPY, "dim"))
|
||||
lines.append(_styled(TUI_RESULT_AGAIN if state.os_type == "android"
|
||||
else TUI_RESULT_COPY, "dim"))
|
||||
return title, lines
|
||||
|
||||
|
||||
# How to copy from the result, under its command while the mouse is on.
|
||||
TUI_RESULT_COPY = ("Shift and a drag select text. All of this is shown again in the "
|
||||
"terminal after Exit.")
|
||||
# Termux has no Shift key under a finger: there the second sentence alone
|
||||
# (tui_audit_14 P3-3).
|
||||
TUI_RESULT_AGAIN = "All of this is shown again in the terminal after Exit."
|
||||
TUI_RESULT_COPY = f"Shift and a drag select text. {TUI_RESULT_AGAIN}"
|
||||
|
||||
|
||||
def _one_kind(state: TuiState, installed: int) -> bool:
|
||||
@@ -18877,6 +19062,7 @@ def _tap_help(state: TuiState) -> list[tuple[str, str]]:
|
||||
if state.screen in _PAGERS and not tui_rows(state):
|
||||
return [("Tap", "A bottom line entry presses its key"),
|
||||
*([("Shift+drag", "Select text to copy")] if state.screen == "result"
|
||||
and state.os_type != "android" and state.pad not in TUI_PAD_ROUTES
|
||||
else [])]
|
||||
# Said at every size, 40x12 included: Install below waits for a second
|
||||
# tap, and the help is where a reader looks when nothing happened.
|
||||
@@ -19256,8 +19442,7 @@ def tui_start(
|
||||
for folder, why in _DETECTION_REFUSED]
|
||||
unsaid = {refused_words(*refused) for refused in _DETECTION_REFUSED}
|
||||
if "esde" in frontends:
|
||||
state.notes.append("ES-DE found: its BIOS files go to each emulator, "
|
||||
"not to ES-DE itself.")
|
||||
state.notes.append(TUI_ESDE_NOTE)
|
||||
state.notes += [line.strip() for line in captured.getvalue().splitlines()
|
||||
if line.strip() and line.strip() not in unsaid]
|
||||
if plat and args.dest is not None:
|
||||
@@ -19774,7 +19959,7 @@ LINE_SAVE_REMEDY = "Name it with --save-dir next time."
|
||||
def _terminal_words(lines: list[str]) -> list[str]:
|
||||
"""The result's lines as the terminal prints them after the screen."""
|
||||
return [line.replace(TUI_SAVE_REMEDY, LINE_SAVE_REMEDY) for line in lines
|
||||
if line != TUI_RESULT_COPY]
|
||||
if line not in (TUI_RESULT_COPY, TUI_RESULT_AGAIN)]
|
||||
|
||||
|
||||
def tui_quit_chosen(state: TuiState) -> bool:
|
||||
|
||||
+1
-1
@@ -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="48a5fca25303757fc2874262c1d176992bd7340da5c59f58c6e911383bf54817"
|
||||
DEFAULT_INSTALL_SHA256="67cbe64d1de7b2f38edd25cab1b34384c55cac77e13f22f4c302fa2b04a809d7"
|
||||
MAX_INSTALLER_BYTES=2097152
|
||||
|
||||
cleanup() {
|
||||
|
||||
@@ -27,8 +27,8 @@
|
||||
{
|
||||
"dest": "roms/ports/retrobios/install.py",
|
||||
"repo_path": "install.py",
|
||||
"size": 929922,
|
||||
"sha256": "48a5fca25303757fc2874262c1d176992bd7340da5c59f58c6e911383bf54817",
|
||||
"size": 939291,
|
||||
"sha256": "67cbe64d1de7b2f38edd25cab1b34384c55cac77e13f22f4c302fa2b04a809d7",
|
||||
"executable": false
|
||||
},
|
||||
{
|
||||
@@ -71,8 +71,8 @@
|
||||
"muos": {
|
||||
"archive": {
|
||||
"dest": "ARCHIVE/RetroBIOS.muxapp",
|
||||
"size": 983099,
|
||||
"sha256": "78658671fe962a5a4efb8ea4a3a50960cc5680b5109391e8536951b5f0a97d14"
|
||||
"size": 992468,
|
||||
"sha256": "06761ebb41ef22fa2c4b64c5e27fab5ed5508dbcd63c5a634527d03a7cdaa7d1"
|
||||
},
|
||||
"members": [
|
||||
{
|
||||
@@ -92,8 +92,8 @@
|
||||
{
|
||||
"name": "RetroBIOS/install.py",
|
||||
"repo_path": "install.py",
|
||||
"size": 929922,
|
||||
"sha256": "48a5fca25303757fc2874262c1d176992bd7340da5c59f58c6e911383bf54817",
|
||||
"size": 939291,
|
||||
"sha256": "67cbe64d1de7b2f38edd25cab1b34384c55cac77e13f22f4c302fa2b04a809d7",
|
||||
"executable": false
|
||||
},
|
||||
{
|
||||
@@ -166,8 +166,8 @@
|
||||
{
|
||||
"dest": ".config/modules/retrobios/install.py",
|
||||
"repo_path": "install.py",
|
||||
"size": 929922,
|
||||
"sha256": "48a5fca25303757fc2874262c1d176992bd7340da5c59f58c6e911383bf54817",
|
||||
"size": 939291,
|
||||
"sha256": "67cbe64d1de7b2f38edd25cab1b34384c55cac77e13f22f4c302fa2b04a809d7",
|
||||
"executable": false
|
||||
},
|
||||
{
|
||||
|
||||
Vendored
+4
-4
@@ -618,8 +618,8 @@
|
||||
|
||||
The same choice without questions:
|
||||
|
||||
python3 install.py --platform retroarch --dest /home/user/.config/retroarch/system --system
|
||||
nintendo-gba
|
||||
python3 install.py --platform retroarch --dest /home/user/.config/retroarch/system \
|
||||
--system nintendo-gba
|
||||
|
||||
|
||||
|
||||
@@ -743,8 +743,8 @@
|
||||
BIOS folder: ^[[0;1m/home/user/.config/retroarch/system^[[0m
|
||||
^[[0;1;31mfailed:^[[0m scph5500.bin (HTTP 404)
|
||||
The files it replaced are kept in /home/user/.config/retroarch/retrobios-backup/2026-10-23_1200
|
||||
To put them back: python3 install.py --restore
|
||||
/home/user/.config/retroarch/retrobios-backup/2026-10-23_1200
|
||||
To put them back: python3 install.py \
|
||||
--restore /home/user/.config/retroarch/retrobios-backup/2026-10-23_1200
|
||||
Same choice without questions:
|
||||
python3 install.py --platform retroarch --dest /home/user/.config/retroarch/system
|
||||
Details: /tmp/retrobios-x/install.log
|
||||
|
||||
Vendored
+6
-6
@@ -9,7 +9,7 @@
|
||||
|
||||
BIOS folder:
|
||||
^[[0;1m/home/user/.config/retroarch/system^[[0m
|
||||
ES-DE found: its BIOS files go to each
|
||||
ES-DE: files go to each emulator.
|
||||
^[[0;1;35mA^[[0m Continue ^[[0;2mB^[[0m Quit ^[[0;2m?^[[0m Help
|
||||
=== platforms
|
||||
^[[0;1m ^[[0;1;35mRetroBIOS^[[0;1m: Which setup?^[[0m
|
||||
@@ -198,11 +198,11 @@
|
||||
|
||||
The same choice without questions:
|
||||
|
||||
python3 install.py --platform
|
||||
retroarch --dest
|
||||
/home/user/.config/retroarch/system
|
||||
python3 install.py \
|
||||
--platform retroarch \
|
||||
--dest .../retroarch/system \
|
||||
--system nintendo-gba
|
||||
|
||||
^[[0;2m Shortened to fit this window.^[[0m
|
||||
|
||||
|
||||
^[[0;1;35mA^[[0m Back
|
||||
@@ -242,7 +242,7 @@
|
||||
The files it replaced are kept in
|
||||
/home/user/.config/retroarch/
|
||||
retrobios-backup/2026-10-23_1200
|
||||
To put them back: python3 install.py
|
||||
To put them back: python3 install.py \
|
||||
^[[0;2m 1-8 of 16^[[0m
|
||||
^[[0;1;35mA^[[0m Exit ^[[0;2m^v^[[0m Scroll
|
||||
=== error
|
||||
|
||||
Vendored
+8
-8
@@ -243,11 +243,11 @@
|
||||
|
||||
The same choice without questions:
|
||||
|
||||
python3 install.py --platform
|
||||
retroarch --dest
|
||||
/home/user/.config/retroarch/system
|
||||
python3 install.py \
|
||||
--platform retroarch \
|
||||
--dest .../retroarch/system \
|
||||
--system nintendo-gba
|
||||
|
||||
^[[0;2m Shortened to fit this window.^[[0m
|
||||
|
||||
|
||||
|
||||
@@ -296,10 +296,10 @@
|
||||
The files it replaced are kept in
|
||||
/home/user/.config/retroarch/
|
||||
retrobios-backup/2026-10-23_1200
|
||||
To put them back: python3 install.py
|
||||
--restore
|
||||
/home/user/.config/retroarch/retrobios-backup/2026-10-23_1200
|
||||
|
||||
To put them back: python3 install.py \
|
||||
--restore .../2026-10-23_1200
|
||||
^[[0;2m Shortened to fit this window.^[[0m
|
||||
Same choice without questions:
|
||||
^[[0;2m 1-11 of 16^[[0m
|
||||
^[[0;1;35mA^[[0m Exit ^[[0;2m^v^[[0m Scroll
|
||||
=== error
|
||||
|
||||
Vendored
+6
-6
@@ -378,8 +378,8 @@
|
||||
|
||||
The same choice without questions:
|
||||
|
||||
python3 install.py --platform retroarch --dest
|
||||
/home/user/.config/retroarch/system --system nintendo-gba
|
||||
python3 install.py --platform retroarch \
|
||||
--dest /home/user/.config/retroarch/system --system nintendo-gba
|
||||
|
||||
|
||||
|
||||
@@ -456,11 +456,11 @@
|
||||
^[[0;1;31mfailed:^[[0m scph5500.bin (HTTP 404)
|
||||
The files it replaced are kept in
|
||||
/home/user/.config/retroarch/retrobios-backup/2026-10-23_1200
|
||||
To put them back: python3 install.py --restore
|
||||
/home/user/.config/retroarch/retrobios-backup/2026-10-23_1200
|
||||
To put them back: python3 install.py \
|
||||
--restore /home/user/.config/retroarch/retrobios-backup/2026-10-23_1200
|
||||
Same choice without questions:
|
||||
python3 install.py --platform retroarch --dest
|
||||
/home/user/.config/retroarch/system
|
||||
python3 install.py --platform retroarch \
|
||||
--dest /home/user/.config/retroarch/system
|
||||
Details: /tmp/retrobios-x/install.log
|
||||
|
||||
|
||||
|
||||
+2
-2
@@ -2,8 +2,8 @@
|
||||
|
||||
The same choice without questions:
|
||||
|
||||
python3 install.py --platform retroarch --dest /home/user/.config/retroarch/system --system
|
||||
nintendo-gba
|
||||
python3 install.py --platform retroarch --dest /home/user/.config/retroarch/system \
|
||||
--system nintendo-gba
|
||||
|
||||
|
||||
|
||||
|
||||
Vendored
+4
-4
@@ -2,11 +2,11 @@
|
||||
|
||||
The same choice without questions:
|
||||
|
||||
python3 install.py --platform
|
||||
retroarch --dest
|
||||
/home/user/.config/retroarch/system
|
||||
python3 install.py \
|
||||
--platform retroarch \
|
||||
--dest .../retroarch/system \
|
||||
--system nintendo-gba
|
||||
|
||||
Shortened to fit this window.
|
||||
|
||||
|
||||
A Back
|
||||
Vendored
+4
-4
@@ -2,11 +2,11 @@
|
||||
|
||||
The same choice without questions:
|
||||
|
||||
python3 install.py --platform
|
||||
retroarch --dest
|
||||
/home/user/.config/retroarch/system
|
||||
python3 install.py \
|
||||
--platform retroarch \
|
||||
--dest .../retroarch/system \
|
||||
--system nintendo-gba
|
||||
|
||||
Shortened to fit this window.
|
||||
|
||||
|
||||
|
||||
|
||||
Vendored
+2
-2
@@ -2,8 +2,8 @@
|
||||
|
||||
The same choice without questions:
|
||||
|
||||
python3 install.py --platform retroarch --dest
|
||||
/home/user/.config/retroarch/system --system nintendo-gba
|
||||
python3 install.py --platform retroarch \
|
||||
--dest /home/user/.config/retroarch/system --system nintendo-gba
|
||||
|
||||
|
||||
|
||||
|
||||
Vendored
+2
-2
@@ -4,8 +4,8 @@
|
||||
BIOS folder: /home/user/.config/retroarch/system
|
||||
failed: scph5500.bin (HTTP 404)
|
||||
The files it replaced are kept in /home/user/.config/retroarch/retrobios-backup/2026-10-23_1200
|
||||
To put them back: python3 install.py --restore
|
||||
/home/user/.config/retroarch/retrobios-backup/2026-10-23_1200
|
||||
To put them back: python3 install.py \
|
||||
--restore /home/user/.config/retroarch/retrobios-backup/2026-10-23_1200
|
||||
Same choice without questions:
|
||||
python3 install.py --platform retroarch --dest /home/user/.config/retroarch/system
|
||||
Details: /tmp/retrobios-x/install.log
|
||||
|
||||
Vendored
+1
-1
@@ -7,6 +7,6 @@
|
||||
The files it replaced are kept in
|
||||
/home/user/.config/retroarch/
|
||||
retrobios-backup/2026-10-23_1200
|
||||
To put them back: python3 install.py
|
||||
To put them back: python3 install.py \
|
||||
1-8 of 16
|
||||
A Exit ^v Scroll
|
||||
Vendored
+4
-4
@@ -7,9 +7,9 @@
|
||||
The files it replaced are kept in
|
||||
/home/user/.config/retroarch/
|
||||
retrobios-backup/2026-10-23_1200
|
||||
To put them back: python3 install.py
|
||||
--restore
|
||||
/home/user/.config/retroarch/retrobios-backup/2026-10-23_1200
|
||||
|
||||
To put them back: python3 install.py \
|
||||
--restore .../2026-10-23_1200
|
||||
Shortened to fit this window.
|
||||
Same choice without questions:
|
||||
1-11 of 16
|
||||
A Exit ^v Scroll
|
||||
Vendored
+4
-4
@@ -5,11 +5,11 @@
|
||||
failed: scph5500.bin (HTTP 404)
|
||||
The files it replaced are kept in
|
||||
/home/user/.config/retroarch/retrobios-backup/2026-10-23_1200
|
||||
To put them back: python3 install.py --restore
|
||||
/home/user/.config/retroarch/retrobios-backup/2026-10-23_1200
|
||||
To put them back: python3 install.py \
|
||||
--restore /home/user/.config/retroarch/retrobios-backup/2026-10-23_1200
|
||||
Same choice without questions:
|
||||
python3 install.py --platform retroarch --dest
|
||||
/home/user/.config/retroarch/system
|
||||
python3 install.py --platform retroarch \
|
||||
--dest /home/user/.config/retroarch/system
|
||||
Details: /tmp/retrobios-x/install.log
|
||||
|
||||
|
||||
|
||||
Vendored
+3
-3
@@ -9,7 +9,7 @@
|
||||
|
||||
BIOS folder:
|
||||
/home/user/.config/retroarch/system
|
||||
ES-DE found: its BIOS files go to each
|
||||
ES-DE: files go to each emulator.
|
||||
A Continue B Quit
|
||||
=== setup 40x15
|
||||
RetroBIOS: Where do you play?
|
||||
@@ -755,8 +755,8 @@
|
||||
|
||||
The same choice without questions:
|
||||
|
||||
python3 install.py --platform retroarch --dest
|
||||
/home/user/.config/retroarch/system --system nintendo-gba
|
||||
python3 install.py --platform retroarch \
|
||||
--dest /home/user/.config/retroarch/system --system nintendo-gba
|
||||
|
||||
|
||||
|
||||
|
||||
+3
-3
@@ -9,7 +9,7 @@
|
||||
|
||||
BIOS folder:
|
||||
/home/user/.config/retroarch/system
|
||||
ES-DE found: its BIOS files go to each
|
||||
ES-DE: files go to each emulator.
|
||||
A Continue B Quit
|
||||
=== setup 40x15
|
||||
RetroBIOS: Where do you play?
|
||||
@@ -755,8 +755,8 @@
|
||||
|
||||
The same choice without questions:
|
||||
|
||||
python3 install.py --platform retroarch --dest
|
||||
/home/user/.config/retroarch/system --system nintendo-gba
|
||||
python3 install.py --platform retroarch \
|
||||
--dest /home/user/.config/retroarch/system --system nintendo-gba
|
||||
|
||||
|
||||
|
||||
|
||||
+3
-3
@@ -9,7 +9,7 @@
|
||||
|
||||
BIOS folder:
|
||||
/home/user/.config/retroarch/system
|
||||
ES-DE found: its BIOS files go to each
|
||||
ES-DE: files go to each emulator.
|
||||
A Continue B Quit
|
||||
=== setup 40x15
|
||||
RetroBIOS: Where do you play?
|
||||
@@ -755,8 +755,8 @@
|
||||
|
||||
The same choice without questions:
|
||||
|
||||
python3 install.py --platform retroarch --dest
|
||||
/home/user/.config/retroarch/system --system nintendo-gba
|
||||
python3 install.py --platform retroarch \
|
||||
--dest /home/user/.config/retroarch/system --system nintendo-gba
|
||||
|
||||
|
||||
|
||||
|
||||
Vendored
+20
-20
@@ -9,7 +9,7 @@
|
||||
|
||||
BIOS folder:
|
||||
/home/user/.config/retroarch/system
|
||||
ES-DE found: its BIOS files go to each
|
||||
ES-DE: files go to each emulator.
|
||||
Enter Continue Esc Quit ? Help
|
||||
=== setup 40x15
|
||||
RetroBIOS: Where do you play?
|
||||
@@ -759,11 +759,11 @@
|
||||
|
||||
The same choice without questions:
|
||||
|
||||
python3 install.py --platform
|
||||
retroarch --dest
|
||||
/home/user/.config/retroarch/system
|
||||
python3 install.py \
|
||||
--platform retroarch \
|
||||
--dest .../retroarch/system \
|
||||
--system nintendo-gba
|
||||
|
||||
Shortened to fit this window.
|
||||
|
||||
|
||||
Enter Back
|
||||
@@ -772,11 +772,11 @@
|
||||
|
||||
The same choice without questions:
|
||||
|
||||
python3 install.py --platform
|
||||
retroarch --dest
|
||||
/home/user/.config/retroarch/system
|
||||
python3 install.py \
|
||||
--platform retroarch \
|
||||
--dest .../retroarch/system \
|
||||
--system nintendo-gba
|
||||
|
||||
Shortened to fit this window.
|
||||
|
||||
|
||||
|
||||
@@ -788,8 +788,8 @@
|
||||
|
||||
The same choice without questions:
|
||||
|
||||
python3 install.py --platform retroarch --dest
|
||||
/home/user/.config/retroarch/system --system nintendo-gba
|
||||
python3 install.py --platform retroarch \
|
||||
--dest /home/user/.config/retroarch/system --system nintendo-gba
|
||||
|
||||
|
||||
|
||||
@@ -926,7 +926,7 @@
|
||||
The files it replaced are kept in
|
||||
/home/user/.config/retroarch/
|
||||
retrobios-backup/2026-10-23_1200
|
||||
To put them back: python3 install.py
|
||||
To put them back: python3 install.py \
|
||||
1-8 of 16
|
||||
Enter Exit ^v Scroll
|
||||
=== result 40x15
|
||||
@@ -939,10 +939,10 @@
|
||||
The files it replaced are kept in
|
||||
/home/user/.config/retroarch/
|
||||
retrobios-backup/2026-10-23_1200
|
||||
To put them back: python3 install.py
|
||||
--restore
|
||||
/home/user/.config/retroarch/retrobios-backup/2026-10-23_1200
|
||||
|
||||
To put them back: python3 install.py \
|
||||
--restore .../2026-10-23_1200
|
||||
Shortened to fit this window.
|
||||
Same choice without questions:
|
||||
1-11 of 16
|
||||
Enter Exit ^v Scroll
|
||||
=== result 80x24
|
||||
@@ -953,11 +953,11 @@
|
||||
failed: scph5500.bin (HTTP 404)
|
||||
The files it replaced are kept in
|
||||
/home/user/.config/retroarch/retrobios-backup/2026-10-23_1200
|
||||
To put them back: python3 install.py --restore
|
||||
/home/user/.config/retroarch/retrobios-backup/2026-10-23_1200
|
||||
To put them back: python3 install.py \
|
||||
--restore /home/user/.config/retroarch/retrobios-backup/2026-10-23_1200
|
||||
Same choice without questions:
|
||||
python3 install.py --platform retroarch --dest
|
||||
/home/user/.config/retroarch/system
|
||||
python3 install.py --platform retroarch \
|
||||
--dest /home/user/.config/retroarch/system
|
||||
Details: /tmp/retrobios-x/install.log
|
||||
|
||||
|
||||
|
||||
Vendored
+3
-3
@@ -9,7 +9,7 @@
|
||||
|
||||
BIOS folder:
|
||||
/home/user/.config/retroarch/system
|
||||
ES-DE found: its BIOS files go to each
|
||||
ES-DE: files go to each emulator.
|
||||
A Continue R2 Quit
|
||||
=== setup 40x15
|
||||
RetroBIOS: Where do you play?
|
||||
@@ -784,8 +784,8 @@
|
||||
|
||||
The same choice without questions:
|
||||
|
||||
python3 install.py --platform retroarch --dest
|
||||
/home/user/.config/retroarch/system --system nintendo-gba
|
||||
python3 install.py --platform retroarch \
|
||||
--dest /home/user/.config/retroarch/system --system nintendo-gba
|
||||
|
||||
|
||||
|
||||
|
||||
Vendored
+11
-11
@@ -9,7 +9,7 @@
|
||||
|
||||
BIOS folder:
|
||||
/home/user/.config/retroarch/system
|
||||
ES-DE found: its BIOS files go to each
|
||||
ES-DE: files go to each emulator.
|
||||
Start Continue B Quit
|
||||
=== setup 40x15
|
||||
RetroBIOS: Where do you play?
|
||||
@@ -813,11 +813,11 @@
|
||||
|
||||
The same choice without questions:
|
||||
|
||||
python3 install.py --platform
|
||||
retroarch --dest
|
||||
/home/user/.config/retroarch/system
|
||||
python3 install.py \
|
||||
--platform retroarch \
|
||||
--dest .../retroarch/system \
|
||||
--system nintendo-gba
|
||||
|
||||
Shortened to fit this window.
|
||||
|
||||
|
||||
Start Back
|
||||
@@ -826,11 +826,11 @@
|
||||
|
||||
The same choice without questions:
|
||||
|
||||
python3 install.py --platform
|
||||
retroarch --dest
|
||||
/home/user/.config/retroarch/system
|
||||
python3 install.py \
|
||||
--platform retroarch \
|
||||
--dest .../retroarch/system \
|
||||
--system nintendo-gba
|
||||
|
||||
Shortened to fit this window.
|
||||
|
||||
|
||||
|
||||
@@ -842,8 +842,8 @@
|
||||
|
||||
The same choice without questions:
|
||||
|
||||
python3 install.py --platform retroarch --dest
|
||||
/home/user/.config/retroarch/system --system nintendo-gba
|
||||
python3 install.py --platform retroarch \
|
||||
--dest /home/user/.config/retroarch/system --system nintendo-gba
|
||||
|
||||
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -8,5 +8,5 @@
|
||||
|
||||
BIOS folder:
|
||||
/home/user/.config/retroarch/system
|
||||
ES-DE found: its BIOS files go to each
|
||||
ES-DE: files go to each emulator.
|
||||
A Continue B Quit ? Help
|
||||
@@ -97,7 +97,7 @@ class InstallerOutputExample(unittest.TestCase):
|
||||
for part in omitted.group(1).split(", "):
|
||||
self.assertIn(part.rsplit(":", 1)[0], install.OMISSION_REASONS.values())
|
||||
count, size = re.search(r"\n (\d+) files \(([\d.]+ [KMG]B)\)", run).groups()
|
||||
self.assertIn(f"0/{count} present", run)
|
||||
self.assertIn(f"0 in place, {count} missing", run)
|
||||
self.assertSplitAddsUp(run, count, size)
|
||||
menu = re.search(r"RetroArch: (\d+) files, ([\d.]+ [KMG]B)[.,]([^`]*)", text)
|
||||
self.assertEqual(menu.groups()[:2], (count, size))
|
||||
@@ -118,10 +118,14 @@ class InstallerOutputExample(unittest.TestCase):
|
||||
r"files \(([\d.]+) ([KMG]B)\)", text)
|
||||
self.assertIsNotNone(split, "a categorized list says its BIOS files and game data")
|
||||
bios, bios_size, bios_unit, games, game_size, game_unit = split.groups()
|
||||
self.assertEqual(int(bios) + int(games), int(count))
|
||||
# The data files in the one total, the third kind (code_audit_21 P3-8).
|
||||
data = re.search(r"and (\d+) data files \(([\d.]+) ([KMG]B)\)", text)
|
||||
data_count, data_size, data_unit = data.groups() if data else ("0", "0", "KB")
|
||||
self.assertEqual(int(bios) + int(games) + int(data_count), int(count))
|
||||
units = {"KB": 1, "MB": 1024, "GB": 1024 ** 2}
|
||||
total, unit = size.split()
|
||||
added = (float(bios_size) * units[bios_unit] + float(game_size) * units[game_unit])
|
||||
added = (float(bios_size) * units[bios_unit] + float(game_size) * units[game_unit]
|
||||
+ float(data_size) * units[data_unit])
|
||||
self.assertLessEqual(abs(added - float(total) * units[unit]), 0.15 * units[unit])
|
||||
|
||||
|
||||
@@ -744,10 +748,10 @@ class FirstInstallWalkthrough(unittest.TestCase):
|
||||
self.assertIn(f'title = "{ending}"', source)
|
||||
self.assertNotRegex(self.text, r"Done\. \d+ files")
|
||||
count, size = re.search(r"\n (\d+) files \(([\d.]+ [KMG]B)\)", step3).groups()
|
||||
self.assertIn(f"0/{count} present", step3)
|
||||
self.assertIn(f"0 in place, {count} missing", step3)
|
||||
self.assertIn(f"Downloading {count} files ({size})", step3)
|
||||
self.assertIn(f"Done. {count} files installed", step4)
|
||||
self.assertIn(f"{count}/{count} present ({count} verified", step5)
|
||||
self.assertIn(f"{count} in place, 0 missing", step5)
|
||||
manifest = json.loads((ROOT / "install" / "batocera.json").read_text(encoding="utf-8"))
|
||||
omitted = re.search(r"Not installable: (\d+) files \((\d+) required", step3)
|
||||
if manifest.get("omitted_files"):
|
||||
|
||||
@@ -3550,7 +3550,7 @@ class ARefusedStandaloneCopyIsNamed(unittest.TestCase):
|
||||
install._report_outcome([bios], 0, 1, failed, 0, False, failures=failures)
|
||||
text = out.getvalue()
|
||||
self.assertIn(str(target / "scph5501.bin"), text)
|
||||
self.assertIn(str(target), text.split("Location")[1])
|
||||
self.assertIn(str(target), text.split("BIOS folder")[-1])
|
||||
self.assertIn("cannot write there", text)
|
||||
|
||||
|
||||
@@ -4534,7 +4534,7 @@ class ANetworkFailureNamesTheNetwork(unittest.TestCase):
|
||||
self.assertEqual(raised.exception.code, 2)
|
||||
text = out.getvalue()
|
||||
self.assertIn("HTTPS_PROXY", text)
|
||||
self.assertIn("Destination: /bios", text)
|
||||
self.assertIn("BIOS folder: /bios", text)
|
||||
self.assertNotIn("being written to", text)
|
||||
|
||||
|
||||
@@ -5225,7 +5225,7 @@ class EventsDescribeTheRun(unittest.TestCase, _SyntheticTree):
|
||||
"interrupted": False},
|
||||
{"event": "end", "code": 1},
|
||||
])
|
||||
self.assertIn("2/4 present (1 verified, 1 wrong hash)", proc.stderr)
|
||||
self.assertIn("1 in place, 2 missing, 1 wrong copy", proc.stderr)
|
||||
self.assertFalse((self.bios / "c.bin").exists())
|
||||
|
||||
def test_an_install_run(self):
|
||||
|
||||
@@ -678,7 +678,7 @@ class AHomeThisUserCannotOpen(unittest.TestCase):
|
||||
with self.subTest(run=name):
|
||||
self.assertNotIn("Traceback", proc.stderr)
|
||||
# A named folder needs no detection and is checked.
|
||||
self.assertIn("0/5 present", runs["named"].stdout)
|
||||
self.assertIn("0 in place, 5 missing", runs["named"].stdout)
|
||||
self.assertIn(f"Could not look for setups: this user cannot open {home}",
|
||||
runs["detected"].stderr)
|
||||
|
||||
@@ -811,7 +811,7 @@ class APartlyClosedHome(unittest.TestCase):
|
||||
self.assertNotIn(str(home / ".config" / "retroarch" / "system"),
|
||||
proc.stdout + proc.stderr)
|
||||
self.assertIn(f"Found RetroArch at {system}", runs["detected"].stdout)
|
||||
self.assertIn("0/5 present", runs["named"].stdout)
|
||||
self.assertIn("0 in place, 5 missing", runs["named"].stdout)
|
||||
self.assertNotIn("not detected", runs["named"].stdout)
|
||||
events = [json.loads(line) for line in runs["events"].stdout.splitlines()]
|
||||
kinds = [event["event"] for event in events]
|
||||
|
||||
@@ -1459,7 +1459,7 @@ class ThePlatformsOwnListAlone(unittest.TestCase):
|
||||
with self.subTest(mode=mode), FakeRepository() as repo:
|
||||
proc = repo.run("--declared-only", mode)
|
||||
self.assertIn("5 to 3 files", proc.stdout, proc.stderr)
|
||||
self.assertIn("0/3 present", proc.stdout)
|
||||
self.assertIn("0 in place, 3 missing", proc.stdout)
|
||||
self.assertEqual(repo.installed(), [])
|
||||
|
||||
def test_it_composes_with_the_other_narrowings(self):
|
||||
|
||||
+185
-14
@@ -771,7 +771,8 @@ class PlanWarnings(unittest.TestCase):
|
||||
self.assertIn("You cannot write to this folder: choose another one.", text)
|
||||
# sudo changes the home folder: the advice names this one.
|
||||
self.assertIn("Or run it as root, this folder named: sudo python3 install.py "
|
||||
f"--platform retroarch --dest {SYSTEM_DIR}", " ".join(text.split()))
|
||||
f"--platform retroarch --dest {SYSTEM_DIR}",
|
||||
" ".join(text.replace(" \\", "").split()))
|
||||
rows = install.tui_rows(state)
|
||||
self.assertEqual([row.action for row in rows[:2]], ["folder", "install"])
|
||||
self.assertEqual(state.focus.get("plan", 0), 0)
|
||||
@@ -3737,8 +3738,9 @@ class AuditFive(unittest.TestCase):
|
||||
saves = {"files": 1, "folder": str(HOME / ".config" / "retroarch" / "saves" / "<core>")}
|
||||
lines = install._saves_lines(state, saves)
|
||||
# In words: "saves/<core>" read as a folder named <core>.
|
||||
# The folder at the BIOS folder's indent (tui_audit_14 P3-9).
|
||||
self.assertEqual(lines, ["1 file read from RetroArch's save directory goes to a "
|
||||
"folder per core under:", "~/.config/retroarch/saves"])
|
||||
"folder per core under:", " ~/.config/retroarch/saves"])
|
||||
|
||||
def test_the_shared_note_is_one_line(self):
|
||||
state = self.long_list()
|
||||
@@ -7578,7 +7580,7 @@ class LineModeAndScreenDecideAlike(_LineModeFindings, _OutsideTextFindings,
|
||||
self.assertIn("g) Game data: kept (3 files, 120 B): leave it out", text)
|
||||
self.assertIn(" 4 files (123 B)\n 1 BIOS file (3 B) and 3 game data files (120 B).",
|
||||
text)
|
||||
self.assertIn("0/4 present", text)
|
||||
self.assertIn("0 in place, 4 missing, 0 wrong copies", text)
|
||||
self.assertIn("4 files (123 B)", text[text.index("Checking existing files"):])
|
||||
|
||||
def test_g_counts_the_systems_chosen_and_leaves_out_what_it_said(self):
|
||||
@@ -9966,8 +9968,12 @@ class TheAndroidResultReadsWhole(unittest.TestCase):
|
||||
lines = [str(line).strip() for line in self.result(cols)]
|
||||
words = [word for line in lines for word in line.split()]
|
||||
self.assertIn("--restore", words)
|
||||
# The command keeps it whole, to be copied as one word.
|
||||
self.assertIn(full, words)
|
||||
# The command keeps it whole where its line holds it, cut to
|
||||
# its last folders and said so where not (tui_audit_14 P3-8).
|
||||
if cols >= 60:
|
||||
self.assertIn(full, words)
|
||||
else:
|
||||
self.assertIn(install.TUI_SHORTENED, lines)
|
||||
# Prose breaks it after a separator only, never inside a name
|
||||
# (tui_audit_13 P3-3): glued again, it is whole.
|
||||
self.assertIn(full, re.sub(r"/ ", "/", " ".join(lines)))
|
||||
@@ -9979,8 +9985,11 @@ class TheAndroidResultReadsWhole(unittest.TestCase):
|
||||
state.stack.append("result")
|
||||
pieces = install._text_lines([command], 38, whole=True)
|
||||
joined = [str(piece).strip() for piece in pieces if piece.style != "cont"]
|
||||
self.assertIn(install.BOOTSTRAP_URL, joined)
|
||||
self.assertIn("'/storage/emulated/0/a b/20261027-223524'", joined)
|
||||
# The address never cut, the option with its value, each line but
|
||||
# the last ending with the shell's continuation.
|
||||
self.assertIn(f"{install.BOOTSTRAP_URL} \\", joined)
|
||||
self.assertTrue(any(line.startswith("--restore '") for line in joined), joined)
|
||||
self.assertTrue(all(len(str(piece)) <= 38 for piece in pieces), pieces)
|
||||
|
||||
def test_a_long_line_folds_in_the_terminal(self):
|
||||
terminal = install._TuiTerminal.__new__(install._TuiTerminal)
|
||||
@@ -10854,10 +10863,12 @@ class AuditNineteenAndTwelve(_CardFixtures, unittest.TestCase):
|
||||
width = len(path) + 1
|
||||
lines = install._wrap(f"BIOS folder: {path}", width, whole="text")
|
||||
self.assertEqual(lines, ["BIOS folder:", path])
|
||||
command = install._wrap(f" python3 install.py --dest {path}", width + 1,
|
||||
# A command keeps the option with its value, the path whole where
|
||||
# the line holds them (tui_audit_14 P3-8).
|
||||
command = install._wrap(f" python3 install.py --dest {path}", width + 10,
|
||||
whole="command")
|
||||
self.assertIn(f" {path}", command)
|
||||
self.assertTrue(all(len(line) <= width + 1 for line in command), command)
|
||||
self.assertIn(f" --dest {path}", command)
|
||||
self.assertTrue(all(len(line) <= width + 10 for line in command), command)
|
||||
|
||||
def test_the_result_spells_each_folder_one_way(self):
|
||||
"""tui_audit_12 P3-7: the save folder as ~/ beside full paths."""
|
||||
@@ -10865,7 +10876,7 @@ class AuditNineteenAndTwelve(_CardFixtures, unittest.TestCase):
|
||||
state.done_saves = {"event": "saves", "files": 2,
|
||||
"folder": "/home/user/.config/retroarch/saves/<core>"}
|
||||
_title, lines = install.tui_outcome(state)
|
||||
self.assertIn("/home/user/.config/retroarch/saves", lines)
|
||||
self.assertIn("/home/user/.config/retroarch/saves", [line.strip() for line in lines])
|
||||
self.assertFalse(any(line.startswith("~") for line in lines))
|
||||
|
||||
def test_minui_names_the_folder_of_its_plan(self):
|
||||
@@ -11506,7 +11517,11 @@ class AuditTwentyAndThirteen(unittest.TestCase):
|
||||
while pieces[-1].endswith("/") and lines[at + len(pieces)]:
|
||||
pieces.append(lines[at + len(pieces)])
|
||||
spelled.append(pieces)
|
||||
self.assertNotIn("...", " ".join(lines))
|
||||
# A command's path may be cut to fit, said so: a folder
|
||||
# in prose never is.
|
||||
self.assertNotIn("...", " ".join(
|
||||
line for line in lines if install.tui_command(state)[:10] not in line
|
||||
and not line.startswith("--")))
|
||||
self.assertTrue(all(len(piece) <= cols - 2 for piece in pieces), pieces)
|
||||
self.assertEqual(spelled[0], spelled[1])
|
||||
self.assertEqual("".join(spelled[0]), f"BIOS folder: {dest}")
|
||||
@@ -11516,9 +11531,9 @@ class AuditTwentyAndThirteen(unittest.TestCase):
|
||||
under an indent of 6."""
|
||||
path = "/home/moi/retroarch_system/tmp/tui_audit13/pack/MinUI_2025-11_BIOS_Pack.zip"
|
||||
self.assertEqual(len(path), 75)
|
||||
lines = install._wrap(f" python3 install.py --from-pack {path}", 78,
|
||||
lines = install._wrap(f" python3 install.py --from-pack {path}", 95,
|
||||
whole="command")
|
||||
self.assertIn(path, lines)
|
||||
self.assertIn(f" --from-pack {path}", lines)
|
||||
|
||||
def test_the_definition_is_said_once_and_never_spliced(self):
|
||||
"""tui_audit_13 P3-5."""
|
||||
@@ -12346,6 +12361,162 @@ class AuditTwentyOneAndFourteen(unittest.TestCase):
|
||||
_title, lines = install.tui_outcome(state)
|
||||
self.assertIn("7 data files not installed: not in RetroArch_BIOS_Pack.zip.", lines)
|
||||
|
||||
def test_a_command_at_40_columns_breaks_between_shell_words(self):
|
||||
"""tui_audit_14 P3-8: the terminal folded commands inside a word,
|
||||
"--platform" ended a line with its value on the next."""
|
||||
words = ["--platform", "retroarch", "--dest", "/a/b/c/system", "--manifest",
|
||||
"/home/moi/x.json", "--system", "nintendo-gba"]
|
||||
text = "printf '%s\\n' " + " ".join(words)
|
||||
with mock.patch.dict(os.environ, {"RETROBIOS_BOOTSTRAP": "sh"}):
|
||||
lines = install._wrap(text, 38, whole="command")
|
||||
self.assertGreater(len(lines), 1)
|
||||
self.assertTrue(all(install.cell_width(line) <= 38 for line in lines), lines)
|
||||
self.assertTrue(all(line.endswith(" \\") for line in lines[:-1]), lines)
|
||||
for line in lines:
|
||||
# An option never ends a line without its value.
|
||||
self.assertFalse(line.removesuffix(" \\").rstrip().endswith(
|
||||
("--platform", "--dest", "--manifest", "--system")), lines)
|
||||
# The shell reads the lines as the command: the same words.
|
||||
proc = subprocess.run(["sh", "-c", "\n".join(lines)], capture_output=True, text=True,
|
||||
check=True)
|
||||
self.assertEqual(proc.stdout.split(), words)
|
||||
# A path wider than a line keeps its last folders, said so, and the
|
||||
# address of the one-liner is never cut.
|
||||
state = _installed_state(Path("/home/moi/retroarch_system/tmp/tui_audit14/sb/a/b/bios"))
|
||||
state.summary["kept"] = [{"folder": "/home/moi/retroarch_system/tmp/tui_audit14/sb/"
|
||||
"retrobios-backup/2026", "files": 1, "bytes": 1}]
|
||||
with mock.patch.dict(os.environ, {"RETROBIOS_BOOTSTRAP": "sh"}):
|
||||
screen = [str(line) for line in install.tui_view(state, 40, 40)]
|
||||
self.assertTrue(all(install.cell_width(line) <= 40 for line in screen), screen)
|
||||
self.assertIn(install.TUI_SHORTENED, " ".join(screen))
|
||||
self.assertIn(install.BOOTSTRAP_URL, " ".join(screen))
|
||||
state.exit_code = 0
|
||||
farewell = "\n".join(install.tui_farewell(state))
|
||||
self.assertIn("/home/moi/retroarch_system/tmp/tui_audit14/sb/a/b/bios", farewell)
|
||||
|
||||
def test_a_folder_on_its_own_line_has_one_indent(self):
|
||||
"""tui_audit_14 P3-9: the save folder at the label's indent, the
|
||||
BIOS folder under it, a path's last piece without its indent."""
|
||||
state = checked_state()
|
||||
state.dest = Path("/home/moi/retroarch_system/tmp/tui_audit14/sb/esmiyoo/home/"
|
||||
".config/retroarch/system")
|
||||
state.saves = {"files": 1, "folder": "/home/moi/retroarch_system/tmp/tui_audit14/"
|
||||
"sb/and2/storage/RetroArch/saves/<core>"}
|
||||
for cols in (40, 80):
|
||||
with self.subTest(cols=cols):
|
||||
lines = [str(line) for line in install.tui_view(state, cols, 40)]
|
||||
folder = next(i for i, line in enumerate(lines) if "BIOS folder:" in line)
|
||||
under = next(i for i, line in enumerate(lines) if "under:" in line)
|
||||
for at in (folder, under):
|
||||
pieces = []
|
||||
for line in lines[at + 1:]:
|
||||
if not line.strip() or "/" not in line or " " in line.strip():
|
||||
break
|
||||
pieces.append(line)
|
||||
indents = {len(piece) - len(piece.lstrip()) for piece in pieces}
|
||||
self.assertLessEqual(len(indents), 1, pieces)
|
||||
if pieces:
|
||||
self.assertEqual(indents, {3}, pieces)
|
||||
|
||||
def test_no_shift_where_there_is_no_shift_key(self):
|
||||
"""tui_audit_14 P3-3: "Shift and a drag" on Android."""
|
||||
for os_type, said in (("linux", True), ("android", False)):
|
||||
with self.subTest(os_type=os_type):
|
||||
state = _installed_state(SYSTEM_DIR)
|
||||
state.os_type, state.mouse = os_type, True
|
||||
_title, lines = install.tui_outcome(state)
|
||||
self.assertEqual(any("Shift" in line for line in lines), said)
|
||||
self.assertIn(install.TUI_RESULT_AGAIN, lines[-1])
|
||||
state.help = True
|
||||
text = " ".join(install.tui_view(state, 80, 24))
|
||||
self.assertEqual("Shift+drag" in text, said)
|
||||
|
||||
def test_the_40x12_setup_never_ends_mid_sentence(self):
|
||||
"""tui_audit_14 P3-5."""
|
||||
state = fresh_state()
|
||||
state.notes = [install.TUI_ESDE_NOTE]
|
||||
text = [str(line).strip() for line in install.tui_view(state, 40, 12)]
|
||||
self.assertIn(install.TUI_ESDE_SHORT, text)
|
||||
self.assertFalse(any(line.startswith("ES-DE found") for line in text), text)
|
||||
text = " ".join(" ".join(install.tui_view(state, 80, 24)).split())
|
||||
self.assertIn(install.TUI_ESDE_NOTE, text)
|
||||
|
||||
def test_the_40x12_plan_keeps_its_folder_and_version_note(self):
|
||||
"""tui_audit_14 P3-6: the plan named ~/.var/app/ as the folder, and
|
||||
the version note was absent."""
|
||||
state = checked_state()
|
||||
state.dest = HOME / ".var/app/org.libretro.RetroArch/config/retroarch/system"
|
||||
state.version = "This list was built for RetroBat 8.3.1, this machine runs 8.2.0.1."
|
||||
lines = [str(line).strip() for line in install.tui_view(state, 40, 12)]
|
||||
joined = "".join(line for line in lines if line.endswith("/") or "system" in line)
|
||||
self.assertIn("~/.var/app/org.libretro.RetroArch/config/retroarch/system", joined)
|
||||
self.assertIn(state.version, " ".join(" ".join(lines).split()))
|
||||
self.assertTrue(any(re.fullmatch(r"1-\d+ of \d+", line) for line in lines), lines)
|
||||
self.assertIn("> Install", lines)
|
||||
|
||||
def test_one_count_said_once_in_both_modes(self):
|
||||
"""tui_audit_14 P3-10: "22 files installed." over "22 files copied
|
||||
from packx.", and line mode's Location, wrong hash and zero count."""
|
||||
state = _installed_state(SYSTEM_DIR)
|
||||
state.summary["installed"] = 5
|
||||
state.finished = [(f"f{n}.bin", True, "") for n in range(5)]
|
||||
state.local_dests = {f"f{n}.bin": ("/media/packx", "") for n in range(5)}
|
||||
_title, lines = install.tui_outcome(state)
|
||||
self.assertEqual(lines[0], "5 files copied from packx.")
|
||||
self.assertNotIn("5 files installed.", lines)
|
||||
files = [self.entry("gba_bios.bin", b"gba", ["nintendo-gba"])]
|
||||
listing = self.listing("retroarch", files)
|
||||
proc = install_run("--platform", "retroarch", "--manifest", str(listing),
|
||||
"--dest", str(self.here), "--check", "--offline")
|
||||
self.assertIn("0 in place, 1 missing, 0 wrong copies", proc.stdout)
|
||||
self.assertNotIn("present (", proc.stdout)
|
||||
source = (REPO_ROOT / "install.py").read_text(encoding="utf-8")
|
||||
self.assertNotIn('say(f"Location', source)
|
||||
self.assertNotIn("already up to date.\")\n", source.split("\nDone.", 1)[-1][:300])
|
||||
|
||||
def test_quit_names_stay_once(self):
|
||||
"""tui_audit_14 P3-11: "A/Enter Stay B/Esc Stay"."""
|
||||
state = fresh_state()
|
||||
state.stack.append("quit")
|
||||
state.focus["quit"] = 1
|
||||
self.assertEqual(str(install.tui_view(state, 80, 24)[-1]).count("Stay"), 1)
|
||||
|
||||
def test_the_yes_stop_example_keeps_yes(self):
|
||||
"""tui_audit_14 P3-12: a script copying the example stopped at a
|
||||
question."""
|
||||
files = [self.entry("gba_bios.bin", b"gba", ["nintendo-gba"])]
|
||||
listing = self.listing("retroarch", files)
|
||||
proc = install_run("--yes", "--manifest", str(listing), "--offline",
|
||||
env={"HOME": str(self.root / "home"), "RETROBIOS_OS": "linux"})
|
||||
example = next(line for line in proc.stderr.splitlines()
|
||||
if "--platform retroarch --dest" in line)
|
||||
self.assertIn(" --yes", example)
|
||||
|
||||
def test_the_systems_question_fits_the_terminal(self):
|
||||
"""code_audit_21 P3-9: 53 of 66 rows wider than 80 columns."""
|
||||
rows = [(f"system-{n}", [{"dest": f"{n}.bin", "size": 10 ** n}]) for n in range(6)]
|
||||
labels = {"system-0": "Sega Dreamcast-based arcade boards, each "
|
||||
"them sega-dreamcast-arcade",
|
||||
**{f"system-{n}": f"Short {n}" for n in range(1, 6)}}
|
||||
with mock.patch.object(install.shutil, "get_terminal_size",
|
||||
return_value=os.terminal_size((80, 24))), \
|
||||
mock.patch.object(install, "_ask", return_value=""), \
|
||||
contextlib.redirect_stdout(io.StringIO()) as out:
|
||||
install._pick("Systems:", rows, labels=labels)
|
||||
printed = out.getvalue().splitlines()
|
||||
self.assertTrue(all(install.cell_width(line) <= 80 for line in printed), printed)
|
||||
# The long label on its own line, its size under it in the column.
|
||||
at = next(i for i, line in enumerate(printed) if "sega-dreamcast-arcade" in line)
|
||||
self.assertNotIn("B", printed[at].split("arcade", 1)[1])
|
||||
sizes = {line.index("1 file") for line in printed[at + 1:] if "1 file" in line}
|
||||
self.assertEqual(len(sizes), 1, printed)
|
||||
|
||||
def test_the_save_dir_sentence_is_written_once(self):
|
||||
"""code_audit_21 P3-10."""
|
||||
source = (REPO_ROOT / "install.py").read_text(encoding="utf-8")
|
||||
self.assertEqual(source.count('"Name it with --save-dir next time."'), 1)
|
||||
self.assertEqual(source.count("args.declared_only or len(files) + game_data_cut"), 1)
|
||||
|
||||
def test_a_missing_folder_does_not_exist_yet_on_every_row(self):
|
||||
"""code_audit_21 P3-7: line mode said "(created)" of folders nobody
|
||||
created, and the screen said "does not exist yet" of the Windows row
|
||||
|
||||
@@ -126,7 +126,7 @@ curl -fsSL https://abdess.github.io/retrobios/i | sh -s -- --check
|
||||
All good:
|
||||
|
||||
```
|
||||
109/109 present (109 verified, 0 wrong hash)
|
||||
109 in place, 0 missing, 0 wrong copies
|
||||
```
|
||||
|
||||
## Undo
|
||||
|
||||
+31
-19
@@ -218,9 +218,15 @@ installer with a signal (SIGTERM), which stops it the same way. Either way the
|
||||
result says how many files were installed, and it stays printed in the terminal
|
||||
once the screen closes, with the command that repeats the run. A run that failed
|
||||
keeps its log and names it, and every other run leaves nothing behind. A path
|
||||
or a command on the result is never split inside a word: one longer than the
|
||||
window takes a line of its own, which the terminal folds, and a command breaks
|
||||
only between its arguments. Every path a run names is spelled the way its BIOS
|
||||
on the result is never split inside a word: one longer than the window takes
|
||||
a line of its own, which the terminal folds. A command breaks only between its
|
||||
arguments, an option kept with its value, each line but the last ending with
|
||||
the continuation its shell reads (a backslash in sh, a backtick in PowerShell), so
|
||||
the lines copied run as one command. Where an option and its path do not fit
|
||||
on one line, the path keeps its last folders after `.../`, the line under it
|
||||
says it was shortened, and the terminal shows it whole after the screen
|
||||
closes. On Android and on a handheld route the result does not mention Shift,
|
||||
which neither has. Every path a run names is spelled the way its BIOS
|
||||
folder was named, the backup folder and the save directory included
|
||||
(`/sdcard/RetroArch` on Android, not the `/storage/emulated/0` it links to).
|
||||
|
||||
@@ -385,12 +391,12 @@ A RetroArch dry run into an empty folder printed this on 2026-10-27:
|
||||
```
|
||||
Fetching file index for retroarch...
|
||||
106 files read from RetroArch's save directory go to a folder per core under /home/me/.config/retroarch/saves
|
||||
8358 files (6.6 GB)
|
||||
3269 BIOS files (4.1 GB) and 5089 game data files (2.5 GB).
|
||||
10994 files (6.6 GB)
|
||||
3269 BIOS files (4.1 GB), 5089 game data files (2.5 GB) and 2636 data files (38.5 MB).
|
||||
Not installable: 37 files (1 required, not found: 37).
|
||||
|
||||
Checking existing files...
|
||||
0/8358 present (0 verified, 0 wrong hash)
|
||||
0 in place, 10994 missing, 0 wrong copies
|
||||
```
|
||||
|
||||
The second line divides the first, and every later count of the run (the
|
||||
@@ -445,13 +451,13 @@ Fetching file index for batocera...
|
||||
Not installable: 399 files (79 required, not found: 399).
|
||||
|
||||
Checking existing files...
|
||||
0/14481 present (0 verified, 0 wrong hash)
|
||||
0 in place, 14481 missing, 0 wrong copies
|
||||
```
|
||||
|
||||
`Not installable` counts the files of the list that the collection does not
|
||||
hold. They are named at the end of the run.
|
||||
|
||||
On a first install the present count is zero. On a later run it counts what
|
||||
On a first install nothing is in place. On a later run the line counts what
|
||||
is already there, and only the rest is downloaded. A download of 1 GB or more
|
||||
is confirmed first (`Download 9.1 GB to /userdata/bios? [Y/n]`).
|
||||
|
||||
@@ -470,8 +476,8 @@ command again picks up where it stopped.
|
||||
### The end of the run
|
||||
|
||||
```
|
||||
Done. 14481 files installed, 0 files already up to date.
|
||||
Location: /userdata/bios
|
||||
Done. 14481 files installed.
|
||||
BIOS folder: /userdata/bios
|
||||
399 files were not installed: the collection does not hold them, or its copy fails their check.
|
||||
Needed to start an emulator (79 files):
|
||||
dark-forces (theforceengine): DARK.GOB, SOUNDS.GOB, SPRITES.GOB, TEXTURES.GOB
|
||||
@@ -502,11 +508,11 @@ When some files came from this machine rather than the network, a line under
|
||||
the wrong place in the BIOS folder, 4 downloaded.`). See
|
||||
[Copy from an old card](#copy-from-an-old-card).
|
||||
|
||||
The word `Done` and a location are the confirmation. Any other ending
|
||||
The word `Done` and the BIOS folder are the confirmation. Any other ending
|
||||
(`Stopped`, `Finished with errors`, `Nothing was installed`) means something
|
||||
was left undone, and the run says which case it was.
|
||||
|
||||
The lines between `Location:` and the `--check` hint name the files of the
|
||||
The lines between `BIOS folder:` and the `--check` hint name the files of the
|
||||
list that the collection does not hold. `Needed to start an emulator` gives,
|
||||
for each system, the emulators that read it and the files they cannot start
|
||||
without. On the run above, all 79 belong to games (Dark Forces, Diablo, Doom 3,
|
||||
@@ -520,14 +526,14 @@ The same command with `--check` reads the folder and writes nothing:
|
||||
|
||||
```
|
||||
Checking existing files...
|
||||
14481/14481 present (14481 verified, 0 wrong hash)
|
||||
14481 in place, 0 missing, 0 wrong copies
|
||||
|
||||
All files up to date.
|
||||
```
|
||||
|
||||
The files the collection does not hold are listed after it again, as long as
|
||||
they are absent from the folder. A number before `wrong hash` means a file is
|
||||
present with contents the platform does not accept. Running the install again
|
||||
they are absent from the folder. A number before `wrong copies` means a file is
|
||||
in place with contents the platform does not accept. Running the install again
|
||||
replaces it and keeps the old one, see
|
||||
[Files you already had](#files-you-already-had).
|
||||
|
||||
@@ -671,7 +677,7 @@ downloaded as usual, or taken from the pack with `--from-pack`. Several
|
||||
file came from:
|
||||
|
||||
```
|
||||
Done. 44 files installed, 0 files already up to date.
|
||||
Done. 44 files installed.
|
||||
38 files copied from E:\, 4 downloaded, 2 still missing.
|
||||
```
|
||||
|
||||
@@ -697,7 +703,7 @@ same choice comes as questions after detection. A RetroArch run with `--plain`
|
||||
printed this on 2026-10-28, `g` then pressed:
|
||||
|
||||
```
|
||||
RetroArch: 8358 files, 6.6 GB, and 2636 data files (38.5 MB).
|
||||
RetroArch: 10994 files, 6.6 GB.
|
||||
3269 BIOS files (4.1 GB), 5089 game data files (2.5 GB) and 2636 data files (38.5 MB).
|
||||
Game data: files a game or a game engine reads, of no use to the other cores. Data files: the files an emulator reads from a data folder of its own, such as Dolphin's Sys and PPSSPP's assets.
|
||||
BIOS folder: /home/me/.config/retroarch/system
|
||||
@@ -710,7 +716,7 @@ RetroArch: 8358 files, 6.6 GB, and 2636 data files (38.5 MB).
|
||||
>
|
||||
Counted again for your choices. Nothing is written before Enter.
|
||||
|
||||
RetroArch: 3269 files, 4.1 GB, and 2636 data files (38.5 MB).
|
||||
RetroArch: 5905 files, 4.1 GB.
|
||||
3269 BIOS files (4.1 GB) and 2636 data files (38.5 MB). 5089 game data files left out.
|
||||
```
|
||||
|
||||
@@ -1175,7 +1181,13 @@ muOS, ArkOS and the others) opens the setup list instead, filtered to the
|
||||
setups that read it, RetroArch first: Enter on it asks RetroArch's folder. At
|
||||
that question a drive is numbered by the folder its system reads (`ONION:
|
||||
BIOS`), a drive nothing names by its `bios` folder (`EASYROMS: bios`), never
|
||||
by its root, which no setup reads. Another system's card or SHARE drive, and a
|
||||
by its root, which no setup reads. A drive no file names that holds a `bios`
|
||||
folder and console folders (`nes`, `gba`, `psx`) is a handheld's card whose
|
||||
own files sit on a part a computer does not open, ArkOS's `EASYROMS` and
|
||||
AmberELEC's `GAMES`: with nothing found here the first screen and line mode
|
||||
say `A card is in the reader (EASYROMS): choose its system`, and its `bios`
|
||||
folder comes first at the folder question, under Enter, before this
|
||||
computer's usual folder. Another system's card or SHARE drive, and a
|
||||
drive two systems fit, are left out, and a line names them: each is reached
|
||||
through its own setup. A folder typed is read from the current
|
||||
one when it is not a full path, and named in full after. `q` quits there too
|
||||
|
||||
@@ -28,13 +28,13 @@ curl -fsSL https://abdess.github.io/retrobios/i | sh -s -- --check
|
||||
Read the count line:
|
||||
|
||||
```
|
||||
1/109 present (0 verified, 1 wrong hash)
|
||||
0 in place, 108 missing, 1 wrong copy
|
||||
```
|
||||
|
||||
- **`wrong hash`**: another region or model, or damaged.
|
||||
- **Fewer present than the total**: files are missing.
|
||||
- **`wrong copy`**: another region or model, or damaged.
|
||||
- **A number before `missing`**: files are missing.
|
||||
- **A line under a file**, like `still zipped`, says the fix.
|
||||
- **`0 wrong hash`, all present**: go to step 3.
|
||||
- **`0 missing, 0 wrong copies`**: go to step 3.
|
||||
|
||||
## 2. Fix the files
|
||||
|
||||
|
||||
Reference in new issue
Block a user