fix selected
This commit is contained in:
parent
4c2c7e3c4a
commit
979ed019d3
1 changed files with 214 additions and 174 deletions
|
|
@ -402,12 +402,15 @@ class WolfWorkflowTab(QWidget):
|
|||
except Exception as exc:
|
||||
log(f" ⚠ could not snapshot original {src.name}: {exc}")
|
||||
|
||||
def _ensure_originals(self, manifest: dict, log, progress=None) -> None:
|
||||
def _ensure_originals(self, manifest: dict, log, progress=None, *, quiet: bool = False) -> None:
|
||||
"""Make sure a pristine snapshot exists; if entries are missing it and the
|
||||
game still has its .wolf archives (packaging renames them to .wolf.bak),
|
||||
rebuild the snapshot by unpacking those archives into originals/."""
|
||||
from util import wolfdawn
|
||||
|
||||
emit = (lambda _msg: None) if quiet else log
|
||||
wolf_log = None if quiet else log
|
||||
|
||||
data_dir = Path(manifest["data_dir"])
|
||||
entries = manifest.get("entries", [])
|
||||
missing = [
|
||||
|
|
@ -426,7 +429,7 @@ class WolfWorkflowTab(QWidget):
|
|||
archives.extend(base.glob(pat))
|
||||
wanted = [a for a in archives if a.name.lower().startswith(("mapdata", "basicdata"))]
|
||||
if not wanted:
|
||||
log(
|
||||
emit(
|
||||
" ⚠ No pristine originals and no .wolf archives to rebuild them from. "
|
||||
"Re-run Step 0 (Unpack + Extract) on the untranslated game so injection "
|
||||
"has an original to work from."
|
||||
|
|
@ -435,7 +438,7 @@ class WolfWorkflowTab(QWidget):
|
|||
|
||||
originals = self._originals_dir()
|
||||
originals.mkdir(parents=True, exist_ok=True)
|
||||
log("Rebuilding pristine originals from the game's .wolf archives …")
|
||||
emit("Rebuilding pristine originals from the game's .wolf archives …")
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
inputs: list[str] = []
|
||||
for arc in wanted:
|
||||
|
|
@ -449,21 +452,22 @@ class WolfWorkflowTab(QWidget):
|
|||
res = wolfdawn.unpack_all(
|
||||
inputs,
|
||||
str(originals),
|
||||
log_fn=log,
|
||||
log_fn=wolf_log,
|
||||
progress_fn=progress,
|
||||
progress_total=len(inputs),
|
||||
)
|
||||
if not res.ok:
|
||||
log(f" ⚠ could not rebuild originals (unpack exit {res.returncode}).")
|
||||
emit(f" ⚠ could not rebuild originals (unpack exit {res.returncode}).")
|
||||
|
||||
def _restore_live_from_originals(
|
||||
self, entries: list[dict], data_dir: Path, log
|
||||
self, entries: list[dict], data_dir: Path, log, *, quiet: bool = False
|
||||
) -> None:
|
||||
"""Copy pristine originals onto live Data/ binaries before names-inject.
|
||||
|
||||
``names-inject`` rewrites every DB pair, CommonEvent.dat, and every map.
|
||||
Resetting from ``wolf_json/originals/`` first keeps them on one baseline.
|
||||
"""
|
||||
emit = (lambda _msg: None) if quiet else log
|
||||
restored = 0
|
||||
for entry in entries:
|
||||
if entry.get("kind") == "names":
|
||||
|
|
@ -471,7 +475,7 @@ class WolfWorkflowTab(QWidget):
|
|||
orig = self._orig_base_for(entry, data_dir)
|
||||
live = Path(entry["base"])
|
||||
if not orig.exists():
|
||||
log(f" ⚠ no pristine original for {entry['json']} — live file left as-is")
|
||||
emit(f" ⚠ no pristine original for {entry['json']} — live file left as-is")
|
||||
continue
|
||||
try:
|
||||
if orig.is_dir():
|
||||
|
|
@ -483,8 +487,8 @@ class WolfWorkflowTab(QWidget):
|
|||
shutil.copy2(orig, live)
|
||||
restored += 1
|
||||
except Exception as exc:
|
||||
log(f" ⚠ could not restore {live.name} from originals: {exc}")
|
||||
if restored:
|
||||
emit(f" ⚠ could not restore {live.name} from originals: {exc}")
|
||||
if restored and not quiet:
|
||||
log(
|
||||
f"Restored {restored} live binary file(s) from {WORK_DIR_NAME}/originals/."
|
||||
)
|
||||
|
|
@ -2211,12 +2215,11 @@ class WolfWorkflowTab(QWidget):
|
|||
layout.addWidget(_make_hr())
|
||||
layout.addWidget(self._subheading("Quick inject — write only specific files into the game"))
|
||||
layout.addWidget(self._desc(
|
||||
"For iterating: after translating a few files, tick just those here and inject them "
|
||||
"straight into the game's Data/ so you can review the git diff in the game project and "
|
||||
"test in-game. Only files that already have a translation in translated/ are listed. "
|
||||
f"{NAMES_JSON} appears here once Phase 0 has translated it (checked by default). "
|
||||
"Name injection resets live Data/ from wolf_json/originals/ first, then applies "
|
||||
"every safe/refs name across all binaries."
|
||||
"For iterating: tick the files you want, then inject. Each selected JSON is "
|
||||
f"copied into {WORK_DIR_NAME}/ and written into its Data/ binary with WolfDawn. "
|
||||
f"{NAMES_JSON} is special: it applies translated name values across every binary "
|
||||
f"(and resets live Data/ from {WORK_DIR_NAME}/originals/ first). "
|
||||
"Only tick the files you actually want to inject."
|
||||
))
|
||||
|
||||
self.inject_list = QListWidget()
|
||||
|
|
@ -2383,16 +2386,13 @@ class WolfWorkflowTab(QWidget):
|
|||
return p
|
||||
return None
|
||||
|
||||
def _repair_inject_json(self, src: Path, log) -> Path:
|
||||
def _repair_inject_json(self, src: Path, log=None) -> Path:
|
||||
"""Auto-repair WOLF inline codes in a translated JSON before inject."""
|
||||
from util.wolfdawn import codes as wolf_codes
|
||||
|
||||
data = json.loads(src.read_text(encoding="utf-8-sig"))
|
||||
data, repairs = wolf_codes.repair_document(data)
|
||||
if repairs:
|
||||
log(
|
||||
f" Auto-repaired {len(repairs)} line(s) with control-code drift in {src.name}"
|
||||
)
|
||||
src.write_text(
|
||||
json.dumps(data, ensure_ascii=False, indent=4) + "\n",
|
||||
encoding="utf-8",
|
||||
|
|
@ -2400,27 +2400,24 @@ class WolfWorkflowTab(QWidget):
|
|||
return src
|
||||
|
||||
def _sync_translated_json_to_wolf_json(
|
||||
self, json_name: str, log, game_json_dir: Path
|
||||
) -> bool:
|
||||
self, json_name: str, game_json_dir: Path
|
||||
) -> tuple[bool, str | None]:
|
||||
"""Copy one translated/ (or files/) JSON into the game's wolf_json/ folder."""
|
||||
src = self._translated_or_source(json_name)
|
||||
if src is None:
|
||||
return False
|
||||
return False, "no translated copy"
|
||||
dest = game_json_dir / json_name
|
||||
try:
|
||||
if src.resolve() == dest.resolve():
|
||||
log(f" ↷ {json_name} already in {WORK_DIR_NAME}/ (same file)")
|
||||
return True
|
||||
return True, None
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(src, dest)
|
||||
log(f" → synced {json_name} into {WORK_DIR_NAME}/")
|
||||
return True
|
||||
return True, None
|
||||
except Exception as exc:
|
||||
log(f" ⚠ could not update {json_name} in {WORK_DIR_NAME}/: {exc}")
|
||||
return False
|
||||
return False, str(exc)
|
||||
|
||||
def _inject_sync_targets(
|
||||
self,
|
||||
|
|
@ -2438,17 +2435,72 @@ class WolfWorkflowTab(QWidget):
|
|||
j for j in only_json if self._translated_or_source(j) is not None
|
||||
)
|
||||
|
||||
def _log_inject_guard_lines(self, mismatches: list[str], untranslated: int | None, log):
|
||||
"""Surface WolfDawn guard failures instead of burying them in the scrollback."""
|
||||
if untranslated:
|
||||
log(f" ⚠ {untranslated} line(s) left untranslated by WolfDawn safety guards")
|
||||
if not mismatches:
|
||||
return
|
||||
log(f" ⚠ {len(mismatches)} control-code mismatch(es) (first 10):")
|
||||
for line in mismatches[:10]:
|
||||
log(f" {line}")
|
||||
if len(mismatches) > 10:
|
||||
log(f" … and {len(mismatches) - 10} more (see log above)")
|
||||
def _emit_inject_summary(
|
||||
self,
|
||||
log,
|
||||
*,
|
||||
injected: list[str],
|
||||
failed: list[tuple[str, str]],
|
||||
skipped_files: int = 0,
|
||||
skipped_lines: int = 0,
|
||||
) -> None:
|
||||
"""Concise inject report: successes, optional guard summary, then failures."""
|
||||
if injected:
|
||||
if len(injected) <= 12:
|
||||
log("Injected: " + ", ".join(injected))
|
||||
else:
|
||||
log(f"Injected: {len(injected)} file(s)")
|
||||
if skipped_files:
|
||||
log(
|
||||
f"Warnings: {skipped_files} file(s) had {skipped_lines} line(s) "
|
||||
"skipped by WolfDawn safety guards"
|
||||
)
|
||||
for name, reason in failed:
|
||||
log(f"Failed: {name} — {reason}")
|
||||
if not injected and not failed:
|
||||
log("Injected: (nothing)")
|
||||
|
||||
def _inject_failure_reason(
|
||||
self,
|
||||
*,
|
||||
drift: int | None = None,
|
||||
exit_code: int | None = None,
|
||||
untranslated: int | None = None,
|
||||
mismatches: int | None = None,
|
||||
no_source: bool = False,
|
||||
no_json: bool = False,
|
||||
no_original: bool = False,
|
||||
names_drift: bool = False,
|
||||
names_stale: bool = False,
|
||||
) -> str:
|
||||
if no_json:
|
||||
return "no translated JSON"
|
||||
if no_source:
|
||||
return "no translated copy to sync"
|
||||
if no_original:
|
||||
return "no pristine original"
|
||||
if names_stale:
|
||||
return (
|
||||
"sources no longer match live Data/ "
|
||||
"(re-run Step 0 extract on the untranslated game)"
|
||||
)
|
||||
if names_drift:
|
||||
return (
|
||||
"0 applied — live Data/ no longer has Japanese name sources "
|
||||
f"(reset from {WORK_DIR_NAME}/originals/ and inject again)"
|
||||
)
|
||||
if drift:
|
||||
return f"0 applied, {drift} skipped as drift"
|
||||
if untranslated or mismatches:
|
||||
parts = []
|
||||
if untranslated:
|
||||
parts.append(f"{untranslated} untranslated line(s)")
|
||||
if mismatches:
|
||||
parts.append(f"{mismatches} control-code mismatch(es)")
|
||||
return "; ".join(parts)
|
||||
if exit_code is not None:
|
||||
return f"wolf exited {exit_code}"
|
||||
return "inject failed"
|
||||
|
||||
def _on_step_changed(self, idx: int):
|
||||
previous = self._current_step_index
|
||||
|
|
@ -2483,7 +2535,7 @@ class WolfWorkflowTab(QWidget):
|
|||
item = QListWidgetItem(json_name)
|
||||
item.setData(Qt.UserRole, json_name)
|
||||
item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
|
||||
default_checked = json_name == NAMES_JSON
|
||||
default_checked = False
|
||||
item.setCheckState(Qt.Checked if default_checked else Qt.Unchecked)
|
||||
self.inject_list.addItem(item)
|
||||
listed += 1
|
||||
|
|
@ -2558,24 +2610,24 @@ class WolfWorkflowTab(QWidget):
|
|||
manifest = self._read_manifest()
|
||||
en_punct = self.en_punct_cb.isChecked()
|
||||
allow_drift = self.drift_cb.isChecked()
|
||||
quick = only_json is not None
|
||||
game_json_dir = self._work_dir()
|
||||
|
||||
def task(log, progress=None):
|
||||
from util import wolfdawn
|
||||
|
||||
# Guarantee a pristine snapshot to inject from (rebuild from archives if
|
||||
# an older extraction predates snapshotting).
|
||||
self._ensure_originals(manifest, log, progress)
|
||||
injected: list[str] = []
|
||||
failures: list[tuple[str, str]] = []
|
||||
skipped_files = 0
|
||||
skipped_lines = 0
|
||||
|
||||
self._ensure_originals(manifest, log, progress, quiet=True)
|
||||
|
||||
entries = manifest["entries"]
|
||||
data_dir = manifest["data_dir"]
|
||||
data_dir_path = Path(data_dir)
|
||||
applied = 0
|
||||
failed = 0
|
||||
guard_failures: list[str] = []
|
||||
|
||||
names_entry = next((e for e in entries if e["kind"] == "names"), None)
|
||||
names_json = names_entry["json"] if names_entry else NAMES_JSON
|
||||
names_src = (
|
||||
self._translated_or_source(names_entry["json"])
|
||||
if names_entry
|
||||
|
|
@ -2588,8 +2640,9 @@ class WolfWorkflowTab(QWidget):
|
|||
)
|
||||
|
||||
if only_json is None or will_names:
|
||||
log(f"Resetting live Data/ from {WORK_DIR_NAME}/originals/ …")
|
||||
self._restore_live_from_originals(entries, data_dir_path, log)
|
||||
self._restore_live_from_originals(
|
||||
entries, data_dir_path, log, quiet=True
|
||||
)
|
||||
|
||||
names_restored = only_json is None or will_names
|
||||
|
||||
|
|
@ -2597,42 +2650,37 @@ class WolfWorkflowTab(QWidget):
|
|||
strings_targets = {
|
||||
e["json"] for e in entries if e.get("kind") != "names"
|
||||
}
|
||||
elif will_names:
|
||||
strings_targets = {
|
||||
e["json"]
|
||||
for e in entries
|
||||
if e.get("kind") != "names"
|
||||
and self._translated_path(e["json"]) is not None
|
||||
}
|
||||
strings_targets |= {j for j in only_json if j != NAMES_JSON}
|
||||
else:
|
||||
strings_targets = set(only_json or ())
|
||||
# Quick inject: only the JSON files the user ticked (names uses
|
||||
# names-inject separately; do not pull in every translated file).
|
||||
strings_targets = {j for j in only_json if j != NAMES_JSON}
|
||||
|
||||
# Non-name documents: inject each pristine original onto its live binary.
|
||||
for entry in entries:
|
||||
if entry["kind"] == "names":
|
||||
continue
|
||||
if entry["json"] not in strings_targets:
|
||||
continue
|
||||
inject_src = self._translated_path(entry["json"])
|
||||
json_name = entry["json"]
|
||||
inject_src = self._translated_path(json_name)
|
||||
if inject_src is None:
|
||||
inject_src = self._translated_or_source(entry["json"])
|
||||
inject_src = self._translated_or_source(json_name)
|
||||
if inject_src is None:
|
||||
log(f" ⚠ no JSON for {entry['json']} — skipped")
|
||||
failures.append(
|
||||
(json_name, self._inject_failure_reason(no_json=True))
|
||||
)
|
||||
continue
|
||||
inject_src = self._repair_inject_json(inject_src, log)
|
||||
out = entry["base"] # live game binary (or txt-dir) we write into
|
||||
inject_src = self._repair_inject_json(inject_src)
|
||||
out = entry["base"]
|
||||
orig = self._orig_base_for(entry, data_dir_path)
|
||||
base = str(orig) if orig.exists() else out
|
||||
if not orig.exists():
|
||||
log(
|
||||
f" ⚠ no pristine original for {entry['json']}; injecting against the "
|
||||
"live binary, which fails if it was already translated."
|
||||
failures.append(
|
||||
(json_name, self._inject_failure_reason(no_original=True))
|
||||
)
|
||||
log(f"Injecting {entry['json']} → {Path(out).name} …")
|
||||
continue
|
||||
res = wolfdawn.strings_inject(
|
||||
str(inject_src), base, out,
|
||||
allow_code_drift=allow_drift, en_punct=en_punct, log_fn=log,
|
||||
allow_code_drift=allow_drift, en_punct=en_punct, log_fn=None,
|
||||
)
|
||||
a, d = wolfdawn.parse_strings_inject_counts(res.stdout, res.stderr)
|
||||
untranslated = wolfdawn.parse_strings_inject_untranslated(
|
||||
|
|
@ -2645,103 +2693,99 @@ class WolfWorkflowTab(QWidget):
|
|||
res.ok and not (a == 0 and (d or 0) > 0)
|
||||
)
|
||||
if strings_ok:
|
||||
applied += 1
|
||||
if mismatches or (untranslated or 0) > 0:
|
||||
guard_failures.append(entry["json"])
|
||||
self._log_inject_guard_lines(mismatches, untranslated, log)
|
||||
if not res.ok and wolfdawn.inject_had_applied(a):
|
||||
log(
|
||||
f" ⚠ {entry['json']}: wolf exited {res.returncode} "
|
||||
f"after applying {a} change(s) — see warnings above"
|
||||
)
|
||||
injected.append(json_name)
|
||||
guard_skips = (untranslated or 0) + len(mismatches)
|
||||
if guard_skips:
|
||||
skipped_files += 1
|
||||
skipped_lines += untranslated or 0
|
||||
elif res.ok:
|
||||
failed += 1
|
||||
guard_failures.append(entry["json"])
|
||||
log(
|
||||
f" ⚠ {entry['json']}: 0 applied, {d} skipped as drift — the base "
|
||||
"looks already translated. Restore the pristine original (re-run "
|
||||
"Step 0 Unpack on the untranslated game) and inject again."
|
||||
)
|
||||
failures.append((
|
||||
json_name,
|
||||
self._inject_failure_reason(drift=d or 0),
|
||||
))
|
||||
else:
|
||||
failed += 1
|
||||
guard_failures.append(entry["json"])
|
||||
self._log_inject_guard_lines(mismatches, untranslated, log)
|
||||
log(f" ⚠ inject exit {res.returncode} for {entry['json']}")
|
||||
failures.append((
|
||||
json_name,
|
||||
self._inject_failure_reason(
|
||||
exit_code=res.returncode,
|
||||
untranslated=untranslated,
|
||||
mismatches=len(mismatches),
|
||||
),
|
||||
))
|
||||
|
||||
# Name values: apply across the whole data dir when translated/names.json
|
||||
# exists. For a quick inject, only include NAMES_JSON when you want names
|
||||
# applied (it rewrites many binaries).
|
||||
if will_names and names_entry and names_src is not None:
|
||||
if not names_restored:
|
||||
log(
|
||||
f"Resetting live Data/ from {WORK_DIR_NAME}/originals/ "
|
||||
"before name injection …"
|
||||
self._restore_live_from_originals(
|
||||
entries, data_dir_path, log, quiet=True
|
||||
)
|
||||
self._restore_live_from_originals(entries, data_dir_path, log)
|
||||
log("Applying translated name values across Data/ …")
|
||||
res = wolfdawn.names_inject(
|
||||
str(names_src), data_dir,
|
||||
allow_code_drift=allow_drift, en_punct=en_punct, log_fn=log,
|
||||
allow_code_drift=allow_drift, en_punct=en_punct, log_fn=None,
|
||||
)
|
||||
out = (res.stdout or "") + (res.stderr or "")
|
||||
a, d = wolfdawn.parse_names_inject_counts(res.stdout, res.stderr)
|
||||
if wolfdawn.inject_had_applied(a):
|
||||
applied += 1
|
||||
if not res.ok:
|
||||
log(
|
||||
f" ⚠ names: wolf exited {res.returncode} after applying "
|
||||
f"{a} name change(s) — see warnings above"
|
||||
)
|
||||
injected.append(names_json)
|
||||
elif res.ok and a == 0:
|
||||
failed += 1
|
||||
log(
|
||||
f" ⚠ names: 0 applied — the live Data/ binaries no longer contain "
|
||||
"the Japanese 'source' strings in names.json (often because a prior "
|
||||
"inject already translated them without resetting from originals). "
|
||||
"Use “Inject all translations”, or tick names.json in quick inject "
|
||||
f"(which resets from {WORK_DIR_NAME}/originals/ first)."
|
||||
)
|
||||
failures.append((
|
||||
names_json,
|
||||
self._inject_failure_reason(names_drift=True),
|
||||
))
|
||||
elif res.ok and (d or 0) > 0:
|
||||
failed += 1
|
||||
log(
|
||||
f" ⚠ names: 0 applied, {d} skipped as drift — sources in "
|
||||
f"{NAMES_JSON} no longer match the live binaries. Re-run Step 0 "
|
||||
"extract on the untranslated game, translate again, and inject."
|
||||
)
|
||||
failures.append((
|
||||
names_json,
|
||||
self._inject_failure_reason(names_stale=True),
|
||||
))
|
||||
else:
|
||||
failed += 1
|
||||
log(f" ⚠ names-inject exit {res.returncode}")
|
||||
failures.append((
|
||||
names_json,
|
||||
self._inject_failure_reason(exit_code=res.returncode),
|
||||
))
|
||||
|
||||
# Refresh wolf_json/ from translated/ for git (full inject: every translated
|
||||
# manifest JSON including names.json; quick inject: only what was selected).
|
||||
sync_targets = self._inject_sync_targets(entries, only_json)
|
||||
synced = 0
|
||||
for json_name in sync_targets:
|
||||
if self._sync_translated_json_to_wolf_json(
|
||||
json_name, log, game_json_dir
|
||||
):
|
||||
synced += 1
|
||||
if synced:
|
||||
log(f"Synced {synced} translated JSON file(s) into {WORK_DIR_NAME}/")
|
||||
ok, err = self._sync_translated_json_to_wolf_json(
|
||||
json_name, game_json_dir
|
||||
)
|
||||
if not ok:
|
||||
failures.append((
|
||||
json_name,
|
||||
self._inject_failure_reason(
|
||||
no_source=err == "no translated copy",
|
||||
)
|
||||
if err == "no translated copy"
|
||||
else f"could not sync to {WORK_DIR_NAME}/ ({err})",
|
||||
))
|
||||
|
||||
msg = f"Injected {applied} document(s)"
|
||||
if guard_failures:
|
||||
msg += f"; {len(guard_failures)} had guard warnings (see log)"
|
||||
if failed:
|
||||
msg += f"; {failed} failed"
|
||||
if quick:
|
||||
msg += ". Review the git diff in the game project and test in-game."
|
||||
else:
|
||||
msg += ". Continue to Step 6 to package."
|
||||
inject_state["applied"] = applied
|
||||
return failed == 0, msg
|
||||
self._emit_inject_summary(
|
||||
log,
|
||||
injected=injected,
|
||||
failed=failures,
|
||||
skipped_files=skipped_files,
|
||||
skipped_lines=skipped_lines,
|
||||
)
|
||||
inject_state["applied"] = len(injected)
|
||||
inject_state["had_failures"] = bool(failures)
|
||||
inject_state["only_json"] = only_json
|
||||
return not failures, ""
|
||||
|
||||
def _after_inject(ok: bool, msg: str):
|
||||
if inject_state.get("applied", 0) > 0 and self._relayout_after_enabled():
|
||||
# Defer until the inject worker has fully finished (busy flag clear).
|
||||
QTimer.singleShot(0, lambda: self._run_relayout(manual=False))
|
||||
selection = inject_state.get("only_json")
|
||||
names_only = (
|
||||
selection is not None
|
||||
and selection
|
||||
and selection.issubset({NAMES_JSON})
|
||||
)
|
||||
if names_only or not self._relayout_after_enabled():
|
||||
self._log("Relayout: not run")
|
||||
return
|
||||
if inject_state.get("applied", 0) <= 0:
|
||||
self._log("Relayout: not run")
|
||||
return
|
||||
QTimer.singleShot(
|
||||
0, lambda: self._run_relayout(manual=False, quiet=True)
|
||||
)
|
||||
|
||||
inject_state: dict = {"applied": 0}
|
||||
inject_state: dict = {"applied": 0, "had_failures": False}
|
||||
self._run_task(task, on_done=_after_inject)
|
||||
|
||||
def _relayout_after_enabled(self) -> bool:
|
||||
|
|
@ -2787,7 +2831,7 @@ class WolfWorkflowTab(QWidget):
|
|||
found.append(p)
|
||||
return found
|
||||
|
||||
def _sync_relayout_tree(self, src_root: Path, dest_root: Path, log) -> int:
|
||||
def _sync_relayout_tree(self, src_root: Path, dest_root: Path, log=None) -> int:
|
||||
"""Copy every file under *src_root* onto *dest_root*, preserving relative paths."""
|
||||
copied = 0
|
||||
for src in src_root.rglob("*"):
|
||||
|
|
@ -2798,10 +2842,9 @@ class WolfWorkflowTab(QWidget):
|
|||
dest.parent.mkdir(parents=True, exist_ok=True)
|
||||
shutil.copy2(src, dest)
|
||||
copied += 1
|
||||
log(f" ← {rel}")
|
||||
return copied
|
||||
|
||||
def _run_relayout(self, *, manual: bool = False):
|
||||
def _run_relayout(self, *, manual: bool = False, quiet: bool = False):
|
||||
"""Reflow Message text (and optionally DB descriptions) on live Data/."""
|
||||
if not self._require_manifest():
|
||||
return
|
||||
|
|
@ -2816,12 +2859,10 @@ class WolfWorkflowTab(QWidget):
|
|||
def task(log, progress=None):
|
||||
from util import wolfdawn
|
||||
|
||||
log(
|
||||
f"Relayout Message text in {data_dir} "
|
||||
f"(width={opts['width']}"
|
||||
+ (f", max-rows={opts['max_rows']}" if opts["max_rows"] else "")
|
||||
+ ") …"
|
||||
)
|
||||
if not quiet:
|
||||
log(f"Relayouting {data_dir} …")
|
||||
|
||||
failures: list[str] = []
|
||||
with tempfile.TemporaryDirectory(prefix="wolfdawn-relayout-") as tmp:
|
||||
out_dir = Path(tmp) / "out"
|
||||
out_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
|
@ -2830,49 +2871,48 @@ class WolfWorkflowTab(QWidget):
|
|||
out_dir=out_dir,
|
||||
width=opts["width"],
|
||||
max_rows=opts["max_rows"] or None,
|
||||
log_fn=log,
|
||||
log_fn=None,
|
||||
)
|
||||
if res.stdout:
|
||||
for line in res.stdout.splitlines():
|
||||
log(line)
|
||||
if not res.ok and res.returncode not in (0, 2):
|
||||
return False, f"relayout exited {res.returncode}"
|
||||
n = self._sync_relayout_tree(out_dir, data_dir, log)
|
||||
log(f"Applied {n} reflowed file(s) onto live Data/.")
|
||||
failures.append(f"message reflow exited {res.returncode}")
|
||||
else:
|
||||
self._sync_relayout_tree(out_dir, data_dir)
|
||||
|
||||
if do_desc:
|
||||
if do_desc and not failures:
|
||||
projects = self._find_db_projects(data_dir)
|
||||
if not projects:
|
||||
log("No DataBase.project / CDataBase.project found — skip desc-relayout.")
|
||||
pass
|
||||
for proj in projects:
|
||||
with tempfile.TemporaryDirectory(prefix="wolfdawn-desc-") as tmp:
|
||||
tmp_path = Path(tmp)
|
||||
out_proj = tmp_path / proj.name
|
||||
log(
|
||||
f"desc-relayout {proj.name} "
|
||||
f"(width={opts['desc_width']}, max-lines={opts['desc_max_lines']}) …"
|
||||
)
|
||||
dres = wolfdawn.desc_relayout(
|
||||
proj,
|
||||
out_proj,
|
||||
width=opts["desc_width"],
|
||||
max_lines=opts["desc_max_lines"],
|
||||
log_fn=log,
|
||||
log_fn=None,
|
||||
)
|
||||
if dres.stdout:
|
||||
for line in dres.stdout.splitlines():
|
||||
log(line)
|
||||
if not dres.ok and dres.returncode not in (0, 2):
|
||||
log(f" ⚠ desc-relayout exit {dres.returncode} for {proj.name}")
|
||||
failures.append(
|
||||
f"desc-relayout exited {dres.returncode} for {proj.name}"
|
||||
)
|
||||
continue
|
||||
# WolfDawn may also write a sibling .dat next to the out .project
|
||||
for produced in tmp_path.iterdir():
|
||||
if not produced.is_file():
|
||||
continue
|
||||
dest = proj.parent / produced.name
|
||||
shutil.copy2(produced, dest)
|
||||
log(f" ← {dest.relative_to(data_dir)}")
|
||||
|
||||
if quiet:
|
||||
if failures:
|
||||
log("Relayout: failed — " + "; ".join(failures))
|
||||
else:
|
||||
log("Relayout: applied")
|
||||
return not failures, ""
|
||||
|
||||
if failures:
|
||||
return False, "Relayout failed — " + "; ".join(failures)
|
||||
return True, "Relayout complete." + (
|
||||
" Continue to Step 6 to package." if not manual else ""
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue