pqcli: add --import-save and --export-save

--import-save FILE adds the character in a .pqw file to the roster;
it can be given more than once, and if any file fails, nothing is
imported. --export-save NUM FILE writes a character out. Export never
overwrites an existing file. The file handling lives in pqw.read_file
and pqw.write_file, so the interfaces can share it.
This commit is contained in:
Dash committed 2026-10-17 15:56:02 +02:00
1 parent 0beb550548
commit 612b223a59
4 files changed
+178 -1

No files matched your search

+62
View File
@@ -5,6 +5,7 @@ from typing import TextIO
from xdg_base_dirs import xdg_config_home
from pqcli import pqw
from pqcli.game_data import (
DataError,
GameData,
@@ -81,6 +82,25 @@ def parse_args() -> argparse.Namespace:
metavar="DIR",
help="copy the built-in word lists to DIR for editing and exit",
)
parser.add_argument(
"--import-save",
type=Path,
action="append",
metavar="FILE",
help=(
"add the character in a Progress Quest web save (.pqw) to the "
"roster and exit; can be given more than once"
),
)
parser.add_argument(
"--export-save",
nargs=2,
metavar=("NUM", "FILE"),
help=(
"write the chosen character to a Progress Quest web save "
"(.pqw) and exit"
),
)
return parser.parse_args()
@@ -106,6 +126,40 @@ def bootstrap_first_run(
raise SystemExit(1)
def import_saves(
roster: Roster, data: GameData, paths: list[Path], use_saves: bool
) -> None:
imported = []
for path in paths:
try:
player, warnings = pqw.read_file(path, data)
except pqw.PqwError as ex:
print(f"pqcli: {ex}", file=sys.stderr)
exit(1)
for warning in warnings:
print(f"pqcli: {path}: {warning}", file=sys.stderr)
imported.append(player)
roster.players.extend(imported)
if use_saves:
roster.save()
for player in imported:
print(f"Imported {player.name}")
def export_save(roster: Roster, data: GameData, num: str, file: str) -> None:
if not num.isdigit() or not 1 <= int(num) <= len(roster.players):
print("Invalid player. Available players:", file=sys.stderr)
list_players(roster, file=sys.stderr)
exit(1)
player = roster.players[int(num) - 1]
try:
pqw.write_file(player, Path(file), data)
except pqw.PqwError as ex:
print(f"pqcli: {ex}", file=sys.stderr)
exit(1)
print(f"Exported {player.name} to {file}")
def main() -> None:
args = parse_args()
@@ -130,6 +184,14 @@ def main() -> None:
list_players(roster)
exit(0)
if args.import_save:
import_saves(roster, data, args.import_save, args.use_saves)
exit(0)
if args.export_save:
export_save(roster, data, *args.export_save)
exit(0)
player: Player | None = None
if args.load_save:
try:
+27
View File
@@ -10,6 +10,7 @@ import binascii
import copy
import datetime
import json
from pathlib import Path
from typing import Any, cast
from pqcli.config import Class, EquipmentType, Monster, Race, StatType
@@ -351,3 +352,29 @@ def export_game(player: Player, data: GameData) -> dict[str, Any]:
"TaskBar": bar(player.task_bar.position, player.task_bar.max_),
"queue": [_encode_queue_entry(task) for task in player.queue],
}
def read_file(path: Path, data: GameData) -> tuple[Player, list[str]]:
"""Import the character in a .pqw file, plus any warnings."""
try:
text = path.read_text(encoding="ascii", errors="replace")
except OSError as ex:
raise PqwError(f"cannot read {path}: {ex.strerror}") from ex
return import_game(decode(text), data)
def write_file(player: Player, path: Path, data: GameData) -> None:
"""Export a character to a .pqw file, never overwriting one."""
text = encode(export_game(player, data))
try:
with path.open("x", encoding="ascii") as handle:
handle.write(text)
except FileExistsError as ex:
raise PqwError(f"{path} already exists") from ex
except OSError as ex:
raise PqwError(f"cannot write {path}: {ex.strerror}") from ex
def default_file_name(player: Player) -> str:
# the web version names its downloads the same way
return f"{player.name}.pqw"