Add text file export along with csv, for easy import into moxfield or other deck building sites

This commit is contained in:
mwisnowski 2025-08-20 11:17:51 -07:00
parent 760c36d75d
commit d9b56d8e12
2 changed files with 98 additions and 1 deletions

View file

@ -205,7 +205,15 @@ class DeckBuilder(CommanderSelectionMixin,
# Export # Export
if hasattr(self, 'export_decklist_csv'): if hasattr(self, 'export_decklist_csv'):
logger.info("Export decklist phase") logger.info("Export decklist phase")
self.export_decklist_csv() csv_path = self.export_decklist_csv()
# Also emit plaintext list (.txt) for quick copy/paste
try:
# Derive matching stem by replacing extension from csv_path
import os as _os
base, _ext = _os.path.splitext(_os.path.basename(csv_path))
self.export_decklist_text(filename=base + '.txt') # type: ignore[attr-defined]
except Exception:
logger.warning("Plaintext export failed (non-fatal)")
end_ts = datetime.datetime.now() end_ts = datetime.datetime.now()
logger.info(f"=== Deck Build: COMPLETE in {(end_ts - start_ts).total_seconds():.2f}s ===") logger.info(f"=== Deck Build: COMPLETE in {(end_ts - start_ts).total_seconds():.2f}s ===")
except KeyboardInterrupt: except KeyboardInterrupt:

View file

@ -190,8 +190,97 @@ class ReportingMixin:
w.writerow(data_row) w.writerow(data_row)
self.output_func(f"Deck exported to {fname}") self.output_func(f"Deck exported to {fname}")
# Auto-generate matching plaintext list (best-effort; ignore failures)
try: # pragma: no cover - sidecar convenience
stem = os.path.splitext(os.path.basename(fname))[0]
# Always overwrite sidecar to reflect latest deck state
self.export_decklist_text(directory=directory, filename=stem + '.txt') # type: ignore[attr-defined]
except Exception:
logger.warning("Plaintext sidecar export failed (non-fatal)")
return fname return fname
def export_decklist_text(self, directory: str = 'deck_files', filename: str | None = None) -> str:
"""Export a simple plaintext list: one line per unique card -> "[Count] [Card Name]".
Naming mirrors CSV export (same stem, .txt extension). Sorting follows same
category precedence then alphabetical within category for consistency.
"""
os.makedirs(directory, exist_ok=True)
# Derive base filename logic (shared with CSV exporter) intentionally duplicated to avoid refactor risk.
if filename is None:
cmdr = getattr(self, 'commander_name', '') or getattr(self, 'commander', '') or ''
cmdr_first = cmdr.split()[0] if cmdr else 'deck'
theme = getattr(self, 'primary_tag', None) or (self.selected_tags[0] if getattr(self, 'selected_tags', []) else None)
theme_first = str(theme).split()[0] if theme else 'notheme'
def _slug(s: str) -> str:
s2 = _re.sub(r'[^A-Za-z0-9_]+', '', s)
return s2 or 'x'
cmdr_slug = _slug(cmdr_first)
theme_slug = _slug(theme_first)
date_part = _dt.date.today().strftime('%Y%m%d')
filename = f"{cmdr_slug}_{theme_slug}_{date_part}.txt"
if not filename.lower().endswith('.txt'):
filename = filename + '.txt'
path = os.path.join(directory, filename)
# Sorting reproduction
precedence_order = [
'Commander', 'Battle', 'Planeswalker', 'Creature', 'Instant', 'Sorcery', 'Artifact', 'Enchantment', 'Land'
]
precedence_index = {k: i for i, k in enumerate(precedence_order)}
commander_name = getattr(self, 'commander_name', '') or getattr(self, 'commander', '') or ''
def classify(primary_type_line: str, card_name: str) -> str:
if commander_name and card_name == commander_name:
return 'Commander'
tl = (primary_type_line or '').lower()
if 'battle' in tl:
return 'Battle'
if 'planeswalker' in tl:
return 'Planeswalker'
if 'creature' in tl:
return 'Creature'
if 'instant' in tl:
return 'Instant'
if 'sorcery' in tl:
return 'Sorcery'
if 'artifact' in tl:
return 'Artifact'
if 'enchantment' in tl:
return 'Enchantment'
if 'land' in tl:
return 'Land'
return 'ZZZ'
# We may want enriched type lines from snapshot; build quick lookup
full_df = getattr(self, '_full_cards_df', None)
combined_df = getattr(self, '_combined_cards_df', None)
snapshot = full_df if full_df is not None else combined_df
row_lookup: Dict[str, any] = {}
if snapshot is not None and not snapshot.empty and 'name' in snapshot.columns:
for _, r in snapshot.iterrows():
nm = str(r.get('name'))
if nm not in row_lookup:
row_lookup[nm] = r
sortable: List[tuple] = []
for name, info in self.card_library.items():
base_type = info.get('Card Type') or info.get('Type','')
row = row_lookup.get(name)
if row is not None:
row_type = row.get('type', row.get('type_line', ''))
if row_type:
base_type = row_type
cat = classify(base_type, name)
prec = precedence_index.get(cat, 999)
sortable.append(((prec, name.lower()), name, info.get('Count',1)))
sortable.sort(key=lambda x: x[0])
with open(path, 'w', encoding='utf-8') as f:
for _, name, count in sortable:
f.write(f"{count} {name}\n")
self.output_func(f"Plaintext deck list exported to {path}")
return path
def print_card_library(self, table: bool = True): # noqa: C901 def print_card_library(self, table: bool = True): # noqa: C901
if table and PrettyTable is None: if table and PrettyTable is None:
table = False table = False