Change card db handling (#286)

* reorganize card db, add doit file
* make card_db_src a true source by removing untranslated entries, removing the untranslated field
* turn update language script into a better compiler
* move tools into submodule
* remove language db comparison test; add travis compile and check-in of card db
Co-authored-by: Nick Vance <nickv2002@users.noreply.github.com>
This commit is contained in:
Peter authored and GitHub committed 2020-01-04 15:26:51 -08:00
1 parent 6fd593f1b6
commit 69c0a66868
57 files changed
+25019 -4677

No files matched your search

File diff suppressed because it is too large. Load diff
+27 -29
View File
@@ -1942,14 +1942,12 @@
"Captain": {
"description": "Now and at the start of your next turn: Play a non-Duration Action card from the Supply costing up to 4 Coins, leaving it there.",
"extra": "On the second turn, you can choose to play a different card, or the same card if there is still a copy of it in the Supply.<n>Captain can only play a visible card in the Supply, and the top card of a pile; it cannot play a card from an empty pile, or a card that has not been uncovered from a split pile, or a card from a split pile that has been bought out, or a non-Supply card (such as Mercenary).<n>The Action card stays in the Supply; if an effect tries to move it (such as Island putting itself on your Island Mat) it will fail to move it.<n>Captain can play a card that trashes itself when played; if the card checks to see if it was trashed (such as Mining Village), it was not, but if the card does not check (such as Acting Troupe), it will function normally.<n>Cards that normally move other cards from the Supply can move themselves when played via Captain; for example playing Workshop can gain itself, and playing Lurker can trash itself.<n>Since the played card is not in play, 'while this is in play' abilities (such as Goons's) will not do anything.<n>If there are no non-Duration Action cards in the Supply that cost up to 4 Coins the turn you play Captain, Captain still stays in play and tries to play a card at the start of your next turn.<n>If Captain plays a card that plays a Duration card, that does not affect which turn Captain is discarded from play.<n>You can enter an infinite loop with Captain, Band of Misfits, Ferry, and cost reduction: With your –2 Coins cost token on the Captain Supply pile, Band of Misfits can be played as Captain, and with cost reduction, Captain can play Band of Misfits. Since this results in Captain being played arbitrarily many times, at the start of your next turn you can play arbitrarily many Actions costing up to 4 Coins.",
"name": "Captain",
"untranslated": "description, extra, name"
"name": "Captain"
},
"Church": {
"description": "+1 Action<br>Set aside up to 3 cards from your hand face down. At the start of your next turn, put them into your hand, then you may trash a card from your hand.",
"extra": "You may set aside zero, one, two, or three cards from your hand. Put them face down; you may look at them.<n>No matter how many cards you set aside, you may trash a card at the start of your next turn and Church is discarded at the end of that turn.<n>The card you trash can be a card you had set aside, or a card that was already in your hand.<n>If you play multiple Churches (or one Church multiple times such as via Throne Room), you may set aside multiple batches of up to three cards. What happens on your next turn is this: you put one batch of set-aside cards in hand, then trash, then put the next batch of set-aside cards in hand, then trash. You can choose which batch of set-aside cards you put in hand first.",
"name": "Church",
"untranslated": "description, extra, name"
"name": "Church"
},
"Dismantle": {
"description": "Entsorge eine Handkarte. Wenn sie 1 Coin oder mehr kostet, nimm eine billigere Karte und ein Gold.",
@@ -2671,44 +2669,44 @@
"extra": "Die Landmarken sind in der Übersicht alphabetisch sortiert. Einmal im Spiel, haben sie für alle Spieler gleichermaßen Gültigkeit. Sie können nicht gekauft werden. Einige Landmarken enthalten Anweisungen für die Spielvorbereitung (unterhalb der Trennlinie). Spielt ihr mit einer dieser Karten, beachtet dies in der Spielvorbereitung. Darfst du dir auf Grund einer Anweisung 1<VP>-Marker von einer Landmarkenkarte oder einem Vorratsstapel nehmen und dort sind zu diesem Zeitpunkt keine 1<VP>d-Marker vorhanden, erhältst du nichts. Sind die zu Spielbeginn platzierten 1<VP>-Marker aufgebraucht, werden keine neuen 1<VP>-Marker platziert.",
"name": "Landmarken (Wahrzeichen)"
},
"Ruined Market": {
"description": "+1 Kauf",
"Abandoned Mine": {
"description": "+1 Coin",
"extra": "",
"name": "Zerstörter Markt",
"untranslated": [
"Note: This card is currently not used."
]
},
"Ruined Village": {
"description": "+1 Aktion",
"extra": "",
"name": "Zerstörtes Dorf",
"untranslated": [
"Note: This card is currently not used."
"name": "Verlassene Mine",
"notes": [
"This card is currently not used."
]
},
"Ruined Library": {
"description": "+1 Karte",
"extra": "",
"name": "Zerstörte Bibliothek",
"untranslated": [
"Note: This card is currently not used."
"notes": [
"This card is currently not used."
]
},
"Ruined Market": {
"description": "+1 Kauf",
"extra": "",
"name": "Zerstörter Markt",
"notes": [
"This card is currently not used."
]
},
"Ruined Village": {
"description": "+1 Aktion",
"extra": "",
"name": "Zerstörtes Dorf",
"notes": [
"This card is currently not used."
]
},
"Survivors": {
"description": "Sieh dir die obersten beiden Karten von deinem Nachziehstapel an. Lege beide Karten ab oder lege beide Karten in beliebiger Reihenfolge zurück auf deinen Nachziehstapel.",
"extra": "",
"name": "Überlebende",
"untranslated": [
"Note: This card is currently not used."
]
},
"Abandoned Mine": {
"description": "+1 Coin",
"extra": "",
"name": "Verlassene Mine",
"untranslated": [
"Note: This card is currently not used."
"notes": [
"This card is currently not used."
]
}
}
+30 -30
View File
@@ -2669,52 +2669,52 @@
"extra": "Landmarks are not Kingdom cards. It is recommended that no more than two Landmarks be used per game. Players may choose how to determine what Landmarks to play with. They may shuffle them with Events and deal out 2 cards from that pile every game; they may shuffle them into the Randomizer deck and use 0-2 depending on how many come up before finding 10 Kingdom cards; or they may use any method they like.",
"name": "Landmarks - All"
},
"Abandoned Mine": {
"description": "+1 Coin",
"extra": "If any Kingdom card has the type Looter (e.g. Cultist, Death Cart, and Marauder), add the all the Ruins cards (Abandoned Mine, Ruined Library, Ruined Market, Ruined Village, Survivors), shuffle, then count 10 per player after the first: 10 for two players, 20 for three players, 30 for four players, and so on. Put the pile face down with the top card face up. Return any remaining Ruins cards to the box.<n>Players can buy Ruins. Ruins cards are Actions; they may be played in the Action phase, and count as Actions for things that refer to Action cards, such as Procession. The Ruins pile, when used, is in the Supply, and if it is empty that counts towards the normal end condition.",
"name": "Abandoned Mine",
"notes": [
"This card is currently not used."
]
},
"Possession 2/2": {
"description": "<justify>During the Possessed turn, whenever one of that player's cards is trashed, set it aside, and that player puts it into his discard pile at the end of the turn, after Clean-up. This counts as the card being trashed, so, for example, you could trash a Mining Village (from Intrigue) and get the 2 coins. Getting those cards back at end of turn does not count as those cards being gained (so for example, you won't get them). Other players' cards that are trashed during that turn are not returned.<n>-If you make another player play an Attack via Possession, that Attack will hit you like it would normally. If you want to use a Reaction in response to that Attack (such as Secret Chamber from Intrigue), you would be the one revealing the Reaction, not the player being Possessed.<n>-Possession is cumulative; if you play it twice in one turn, there will be two extra turns after this one.<n>-Cards passed with Masquerade (from Intrigue) are not being gained or trashed, and so are passed normally. Cards returned to the Supply with Ambassador (from Seaside) are also not being trashed, and so return to the Supply normally.</justify>",
"extra": "Possession causes an extra turn to be played, like the card Outpost does (from Seaside). The extra turn happens only after this turn is completely over - you will have discarded everything and drawn your next hand. Outpost only prevents itself from giving a player two consecutive turns, it does not prevent other cards or the rules from doing so. So, for example, if you play Possession in a two player game, then after the Possession turn, that player still gets his normal turn. If he played Outpost during that turn though, it would not give him an extra turn. If you play both Outpost and Possession in the same turn, the Outpost turn happens first. If you make someone play Outpost during a turn in which you Possessed them, that player will get the extra turn and make decisions during it and so forth, not you; if you make someone play Possession during a turn in which you Possessed them, that will make that player Possess the player to his left, rather than you getting to Possess anyone further. Possession turns (and other extra turns) do not count for the tiebreaker. Once the game ends, no further turns are played, including extra turns from Possession and Outpost. -Unlike Outpost, Possession is not a Duration card. It is discarded in the Clean-up phase of the turn you played it. [Possession also gives the Possessing player all tokens the Possessed player would get, including _ Coins, <VP>, and Debt tokens.]",
"name": "Possession 2/2",
"untranslated": [
"Note: This card is currently not used."
]
},
"Ruined Market": {
"description": "+1 Buy",
"extra": "If any Kingdom card has the type Looter (e.g. Cultist, Death Cart, and Marauder), add the all the Ruins cards (Abandoned Mine, Ruined Library, Ruined Market, Ruined Village, Survivors), shuffle, then count 10 per player after the first: 10 for two players, 20 for three players, 30 for four players, and so on. Put the pile face down with the top card face up. Return any remaining Ruins cards to the box.<n>Players can buy Ruins. Ruins cards are Actions; they may be played in the Action phase, and count as Actions for things that refer to Action cards, such as Procession. The Ruins pile, when used, is in the Supply, and if it is empty that counts towards the normal end condition.",
"name": "Ruined Market",
"untranslated": [
"Note: This card is currently not used."
]
},
"Ruined Village": {
"description": "+1 Action",
"extra": "If any Kingdom card has the type Looter (e.g. Cultist, Death Cart, and Marauder), add the all the Ruins cards (Abandoned Mine, Ruined Library, Ruined Market, Ruined Village, Survivors), shuffle, then count 10 per player after the first: 10 for two players, 20 for three players, 30 for four players, and so on. Put the pile face down with the top card face up. Return any remaining Ruins cards to the box.<n>Players can buy Ruins. Ruins cards are Actions; they may be played in the Action phase, and count as Actions for things that refer to Action cards, such as Procession. The Ruins pile, when used, is in the Supply, and if it is empty that counts towards the normal end condition.",
"name": "Ruined Village",
"untranslated": [
"Note: This card is currently not used."
"notes": [
"This card is currently not used."
]
},
"Ruined Library": {
"description": "+1 Card",
"extra": "If any Kingdom card has the type Looter (e.g. Cultist, Death Cart, and Marauder), add the all the Ruins cards (Abandoned Mine, Ruined Library, Ruined Market, Ruined Village, Survivors), shuffle, then count 10 per player after the first: 10 for two players, 20 for three players, 30 for four players, and so on. Put the pile face down with the top card face up. Return any remaining Ruins cards to the box.<n>Players can buy Ruins. Ruins cards are Actions; they may be played in the Action phase, and count as Actions for things that refer to Action cards, such as Procession. The Ruins pile, when used, is in the Supply, and if it is empty that counts towards the normal end condition.",
"name": "Ruined Library",
"untranslated": [
"Note: This card is currently not used."
"notes": [
"This card is currently not used."
]
},
"Ruined Market": {
"description": "+1 Buy",
"extra": "If any Kingdom card has the type Looter (e.g. Cultist, Death Cart, and Marauder), add the all the Ruins cards (Abandoned Mine, Ruined Library, Ruined Market, Ruined Village, Survivors), shuffle, then count 10 per player after the first: 10 for two players, 20 for three players, 30 for four players, and so on. Put the pile face down with the top card face up. Return any remaining Ruins cards to the box.<n>Players can buy Ruins. Ruins cards are Actions; they may be played in the Action phase, and count as Actions for things that refer to Action cards, such as Procession. The Ruins pile, when used, is in the Supply, and if it is empty that counts towards the normal end condition.",
"name": "Ruined Market",
"notes": [
"This card is currently not used."
]
},
"Ruined Village": {
"description": "+1 Action",
"extra": "If any Kingdom card has the type Looter (e.g. Cultist, Death Cart, and Marauder), add the all the Ruins cards (Abandoned Mine, Ruined Library, Ruined Market, Ruined Village, Survivors), shuffle, then count 10 per player after the first: 10 for two players, 20 for three players, 30 for four players, and so on. Put the pile face down with the top card face up. Return any remaining Ruins cards to the box.<n>Players can buy Ruins. Ruins cards are Actions; they may be played in the Action phase, and count as Actions for things that refer to Action cards, such as Procession. The Ruins pile, when used, is in the Supply, and if it is empty that counts towards the normal end condition.",
"name": "Ruined Village",
"notes": [
"This card is currently not used."
]
},
"Survivors": {
"description": "Look at the top 2 cards of your deck. Discard them or put them back in any order.",
"extra": "If any Kingdom card has the type Looter (e.g. Cultist, Death Cart, and Marauder), add the all the Ruins cards (Abandoned Mine, Ruined Library, Ruined Market, Ruined Village, Survivors), shuffle, then count 10 per player after the first: 10 for two players, 20 for three players, 30 for four players, and so on. Put the pile face down with the top card face up. Return any remaining Ruins cards to the box.<n>Players can buy Ruins. Ruins cards are Actions; they may be played in the Action phase, and count as Actions for things that refer to Action cards, such as Procession. The Ruins pile, when used, is in the Supply, and if it is empty that counts towards the normal end condition.",
"name": "Survivors",
"untranslated": [
"Note: This card is currently not used."
]
},
"Abandoned Mine": {
"description": "+1 Coin",
"extra": "If any Kingdom card has the type Looter (e.g. Cultist, Death Cart, and Marauder), add the all the Ruins cards (Abandoned Mine, Ruined Library, Ruined Market, Ruined Village, Survivors), shuffle, then count 10 per player after the first: 10 for two players, 20 for three players, 30 for four players, and so on. Put the pile face down with the top card face up. Return any remaining Ruins cards to the box.<n>Players can buy Ruins. Ruins cards are Actions; they may be played in the Action phase, and count as Actions for things that refer to Action cards, such as Procession. The Ruins pile, when used, is in the Supply, and if it is empty that counts towards the normal end condition.",
"name": "Abandoned Mine",
"untranslated": [
"Note: This card is currently not used."
"notes": [
"This card is currently not used."
]
}
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+15
View File
@@ -0,0 +1,15 @@
## Fonts
I believe I cannot distribute one font (Minion Pro) domdiv uses as they are owned by Adobe with a License that I understand to disallow redistribution. However, you can download the [3 font files from a third party here](https://www.dropbox.com/s/tsqk69mayoa3pfz/MinionPro-ForDominionTabs.zip?dl=1).
Other sources for the font files (included for historical record but probably unneeded as long as long as the download above works:
- http://fontsgeek.com/fonts/Minion-Pro-Regular
- http://fontsgeek.com/fonts/Minion-Pro-Italic
- http://fontsgeek.com/fonts/Minion-Pro-Bold
Alternatively, the font files are included with every install of the free Adobe Reader. For example, on Windows 7 they are in C:\Program Files (x86)\Adobe\Reader 9.0\Resource\Font called `MinionPro-Regular.otf`, `MinionPro-Bold.otf` and `MinionPro-It.otf`.
Sadly, all these fonts use features that are not support by the reportlab package. Thus, they need to first be converted to ttf (TrueType) format. I used the open source package fontforge to do the conversion. Included as 'convert.ff' is a script for fontforge to do the conversion, on Mac OS X with fontforge installed through macports or homebrew you can just run `./convert.ff MinionPro-Regular.otf`, `./convert.ff MinionPro-Bold.otf` and `./convert.ff MinionPro-It.otf`. With other fontforge installations, you'll need to change the first line of convert.ff to point to your fontforge executable. I have not done this step under Windows - I imagine it may be possible with a cygwin install of fontforge or some such method.
Copy the converted `.ttf` files to the `fonts` directory in the `domdiv` package/directory, then perform the package install below.
View File
Whitespace-only changes.
+115
View File
@@ -0,0 +1,115 @@
import os
import codecs
import json
import sys
import io
import csv
from shutil import copyfile
LANGUAGE_DEFAULT = "en_us" # default language, which takes priority
LANGUAGE_XX = "xx" # language for starting a translation
card_db_dir = os.path.join("..", "domdiv", "card_db") # directory of card data
output_dir = os.path.join(".", "card_db") # directory for output data
crossReference = "./CrossReference"
if len(sys.argv) > 1:
LANGUAGE_NEW = sys.argv[1].strip().lower()
else:
print("Usage: ", sys.argv[0], " xx")
print("where xx is the two letter language abreviation.")
sys.exit()
def unicode_csv_reader(utf8_data, dialect=csv.excel, **kwargs):
csv_reader = csv.reader(utf8_data, dialect=dialect, **kwargs)
for row in csv_reader:
yield [cell.decode("iso-8859-15") for cell in row]
def get_json_data(json_file_path):
# Read in the json from the specified file
with codecs.open(json_file_path, "r", "utf-8") as json_file:
data = json.load(json_file)
assert data, "Could not load json at: '%r' " % json_file_path
return data
def json_dict_entry(entry, separator=""):
# Return a nicely formated json dict entry.
# It does not include the enclosing {} and removes trailing white space
json_data = json.dumps(entry, indent=4, ensure_ascii=False, sort_keys=True)
json_data = json_data.strip(
"{}"
).rstrip() # Remove outer{} and then trailing whitespace
return separator + json_data
def find_index(items, find):
if find in items:
return items.index(find)
else:
return None
reader = unicode_csv_reader(open(crossReference + ".csv"))
cards = list(reader)
headers = cards[0]
if not os.path.exists(output_dir):
os.makedirs(output_dir)
lang_dir = os.path.join(output_dir, LANGUAGE_NEW)
if not os.path.exists(lang_dir):
os.makedirs(lang_dir)
for f in ["bonuses_", "cards_", "sets_", "types_"]:
copyfile(
os.path.join(card_db_dir, LANGUAGE_XX, f + LANGUAGE_XX + ".json"),
os.path.join(output_dir, LANGUAGE_NEW, f + LANGUAGE_NEW + ".json"),
)
lang_index = find_index(headers, LANGUAGE_NEW)
card_index = find_index(headers, "card_tag")
if lang_index is None or card_index is None:
print("Cound not find new language '", LANGUAGE_NEW, "' in the CrossReference file")
print("Files copied, but no name changes were made.")
sys.exit()
# Get the new names for those in the CrossReference
cardlist = {}
for row in cards:
if row[lang_index]:
cardlist[row[card_index]] = row[lang_index]
# Update the names based upon names in the cross reference file
fname = os.path.join(card_db_dir, LANGUAGE_XX, "cards_" + LANGUAGE_XX + ".json")
if os.path.isfile(fname):
lang_data = get_json_data(fname)
else:
print("ERROR: Failed to open :", fname)
sys.exit()
if cardlist:
with io.open(
os.path.join(output_dir, LANGUAGE_NEW, "cards_" + LANGUAGE_NEW + ".json"),
"w",
encoding="utf-8",
) as lang_out:
lang_out.write("{") # Start of set
sep = ""
for card in lang_data:
# print card, lang_data[card]
data = lang_data[card]
if card in cardlist:
# Have a new name, so update name
data["name"] = cardlist[card]
fields = ["description", "extra"] # but no u"name"
data["untranslated"] = fields
lang_out.write(json_dict_entry({card: data}, sep))
sep = ","
lang_out.write("\n}\n") # End of Set
+13
View File
@@ -0,0 +1,13 @@
import sys
from domdiv.tools.common import get_languages, load_language_cards
def check_languages(card_db_dir):
languages = get_languages(card_db_dir)
for lang in languages:
_ = load_language_cards(lang, card_db_dir)
if __name__ == "__main__":
check_languages(sys.argv[1])
+95
View File
@@ -0,0 +1,95 @@
import codecs
import io
import json
import os
LANGUAGE_DEFAULT = "en_us" # default language, which takes priority
LANGUAGE_XX = "xx" # language for starting a translation
# Multikey sort
# see: http://stackoverflow.com/questions/1143671/python-sorting-list-of-dictionaries-by-multiple-keys
def multikeysort(items, columns):
from operator import itemgetter
for c in columns[::-1]:
items = sorted(items, key=itemgetter(c))
return items
def get_languages(card_db_dir):
languages = get_lang_dirs(card_db_dir)
languages.remove(LANGUAGE_DEFAULT)
languages.insert(0, LANGUAGE_DEFAULT)
if LANGUAGE_XX not in languages:
languages.append(LANGUAGE_XX)
return languages
def get_lang_dirs(path):
# Find all valid languages.
lang_dirs = []
for name in os.listdir(path):
dir_path = os.path.join(path, name)
if os.path.isdir(dir_path):
cards_file = os.path.join(dir_path, "cards_" + name + ".json")
sets_file = os.path.join(dir_path, "sets_" + name + ".json")
if os.path.isfile(cards_file) and os.path.isfile(sets_file):
lang_dirs.append(name)
return lang_dirs
def get_json_data(json_file_path):
print(("reading {}".format(json_file_path)))
# Read in the json from the specified file
with codecs.open(json_file_path, "r", "utf-8") as json_file:
data = json.load(json_file)
assert data, "Could not load json at: '%r' " % json_file_path
return data
def load_language_cards(lang, card_db_dir):
# contruct the cards json file name
lang_file = f"cards_{lang}.json"
fname = os.path.join(card_db_dir, lang, lang_file)
if os.path.isfile(fname):
lang_data = get_json_data(fname)
else:
lang_data = {}
return lang_data
def write_data(data, fname):
# Process the file
print(f"writing {fname}")
with io.open(os.path.join(fname), "w", encoding="utf-8") as lang_out:
json.dump(data, lang_out, indent=4, ensure_ascii=False)
lang_out.write("\n")
def write_language_cards(cards, lang, card_db_dir):
lang_file = f"cards_{lang}.json"
fname = os.path.join(card_db_dir, lang, lang_file)
write_data(cards, fname)
def load_card_data(card_db_dir):
###########################################################################
# Get the cards_db information
# Store in a list in the order found in cards[]. Ordered as follows:
# 1. card_tags, 2. group_tags, 3. super groups
###########################################################################
# Get the card data
card_data = get_json_data(os.path.join(card_db_dir, "cards_db.json"))
# Sort the cardset_tags
for card in card_data:
card["cardset_tags"].sort()
# But put all the base cards together by moving to front of the list
if "base" in card["cardset_tags"]:
card["cardset_tags"].remove("base")
card["cardset_tags"].insert(0, "base")
# Sort the cards by cardset_tags, then card_tag
return multikeysort(card_data, ["cardset_tags", "card_tag"])
+48
View File
@@ -0,0 +1,48 @@
# coding=utf-8
import csv
import sys
import json
converted = []
typemap = {
"Aktion": "Action",
"Geld": "Treasure",
"Fluch": "Curse",
"Punkte": "Victory",
"Reaktion": "Reaction",
"Angriff": "Attack",
"Dauer": "Duration",
"Plündern": "Looter",
"Ritter": "Knight",
"Ruine": "Ruins",
"Unterschlupf": "Shelter",
"Reisender": "Traveller",
"Reserve": "Reserve",
}
with open(sys.argv[1], "U") as csvfile:
reader = csv.DictReader(csvfile)
for row in reader:
if not row["Typ"]:
continue
# row = {k: v.decode('ISO-8859-2').encode('utf-8') if type(v) == str else v for k, v in row.iteritems()}
# print chardet.detect(row['Kartentext'])
if "Ritter" in row["Typ"] and row["Kartenname"] != "Ritter":
continue
converted_row = {
"name": row["Kartenname"],
"cost": row["Kosten"],
"cardset": row["Edition"],
"description": row["Kartentext"],
"extra": row["Lange Erklärung"],
"types": [
typemap[t.strip()]
for t in row["Typ"].split("/")
if not t.strip() == "Ritter"
],
"potcost": row.get("potcost", 0),
}
converted.append(converted_row)
json.dump(converted, open(sys.argv[2], "wb"), indent=True)
+297
View File
@@ -0,0 +1,297 @@
###########################################################################
# This file provides maintenance on the various language files
# 1. Create new "xx/cards_xx.json" files that have entries ordered as:
# a. the card_tag entries in "cards_db.json"
# b. the group_tag entries as found in "cards_db.json"
# c. the super group entries (grouping across all expansions"
# d. any unused entries existing in the file (assumed to be work in progress)
#
# 2. Create new "sets_db.json" and "xx/cards_xx.json" with entries sorted alphabetically
#
# All output is in the designated output directory. Original files are not overwritten.
###########################################################################
import os
import os.path
from shutil import copyfile
import argparse
import collections
from domdiv.tools.common import (
get_json_data,
load_language_cards,
LANGUAGE_XX,
LANGUAGE_DEFAULT,
get_languages,
multikeysort,
load_card_data,
write_data,
write_language_cards,
)
def main(card_db_dir, output_dir):
###########################################################################
# Get all the languages, and place the default language first in the list
###########################################################################
languages = get_languages(card_db_dir)
print("Languages:")
print(languages)
print()
###########################################################################
# Make sure the directories exist to hold the output
###########################################################################
# main output directory
if not os.path.exists(output_dir):
os.makedirs(output_dir)
# each language directory
for lang in languages:
# Make sure the directory is there to hold the file
lang_dir = os.path.join(output_dir, lang)
if not os.path.exists(lang_dir):
os.makedirs(lang_dir)
###########################################################################
# Get the types_db information
# Store in a list in the order found in types[]. Ordered by card_type
# 1. card_tags, 2. group_tags, 3. super groups
###########################################################################
type_parts = set()
# Get the card data
type_data = get_json_data(os.path.join(card_db_dir, "types_db.json"))
# Sort the cards by cardset_tags, then card_tag
sorted_type_data = multikeysort(type_data, ["card_type"])
write_data(sorted_type_data, os.path.join(output_dir, "types_db.json"))
type_parts = list(set().union(*[set(t["card_type"]) for t in sorted_type_data]))
type_parts.sort()
print("Unique Types:")
print(type_parts)
print()
###########################################################################
# Get the labels_db information
# Store in a list in the order found.
###########################################################################
all_labels = []
# Get the card data
label_data = get_json_data(os.path.join(card_db_dir, "labels_db.json"))
all_labels = list(set().union(*[set(label["names"]) for label in label_data]))
write_data(label_data, os.path.join(output_dir, "labels_db.json"))
all_labels.sort()
print("Labels: ")
print(all_labels)
print()
###########################################################################
# Fix up all the xx/types_xx.json files
# Place entries in alphabetical order
# If entries don't exist:
# If the default language, set from information in the "types_db.json" file,
# If not the default language, set based on information from the default language.
# Lastly, keep any extra entries that are not currently used, just in case needed
# in the future or is a work in progress.
###########################################################################
for lang in languages:
lang_file = "types_" + lang + ".json"
fname = os.path.join(card_db_dir, lang, lang_file)
if os.path.isfile(fname):
lang_type_data = get_json_data(fname)
else:
lang_type_data = {}
for t in sorted(type_parts):
if t not in lang_type_data:
if lang == LANGUAGE_DEFAULT:
lang_type_data[t] = t
lang_type_default = lang_type_data
else:
lang_type_data[t] = lang_type_default[t]
write_data(lang_type_data, os.path.join(output_dir, lang, lang_file))
if lang == LANGUAGE_DEFAULT:
lang_type_default = lang_type_data # Keep for later languages
sorted_card_data = load_card_data(card_db_dir)
groups = set(card["group_tag"] for card in sorted_card_data if "group_tag" in card)
super_groups = set(["events", "landmarks"])
write_data(sorted_card_data, os.path.join(output_dir, "cards_db.json"))
# maintain the sorted order, but expand with groups and super_groups
cards = [c["card_tag"] for c in sorted_card_data]
cards.extend(sorted(groups))
cards.extend(sorted(super_groups))
print("Cards:")
print(cards)
print()
###########################################################################
# Fix up all the cards_xx.json files
# Place entries in the same order as given in "cards_db.json".
# If entries don't exist:
# If the default language, set base on information in the "cards_db.json" file,
# If not the default language, set based on information from the default language.
# Lastly, keep any extra entries that are not currently used, just in case needed
# in the future or is a work in progress.
###########################################################################
for lang in languages:
# contruct the cards json file name
lang_data = load_language_cards(lang, card_db_dir)
sorted_lang_data = collections.OrderedDict()
for card_tag in cards:
lang_card = lang_data.get(card_tag)
# print(f'looking at {card_tag}: {lang_card}')
if not lang_card or lang == LANGUAGE_XX:
# Card is missing, need to add it
lang_card = {}
if lang == LANGUAGE_DEFAULT:
# Default language gets bare minimum. Really need to add by hand.
lang_card["extra"] = ""
lang_card["name"] = card_tag
lang_card["description"] = ""
lang_default = lang_data
else:
# All other languages should get the default languages' text
lang_card = lang_default[card_tag].copy()
elif lang != LANGUAGE_DEFAULT:
# Card exists, figure out what needs updating
lang_card.update(
{
field: value
for field, value in lang_default[card_tag].items()
if field not in lang_card
}
)
sorted_lang_data[card_tag] = lang_card
unused = set(lang_data) - set(sorted_lang_data)
print(
f"unused in {lang}: {len(unused)}, used: {len(set(lang_data) & set(sorted_lang_data))}"
)
print(unused)
# Now keep any unused values just in case needed in the future
for card_tag in sorted(unused):
lang_card = lang_data.get(card_tag)
lang_card["notes"] = ["This card is currently not used."]
sorted_lang_data[card_tag] = lang_card
write_language_cards(sorted_lang_data, lang, output_dir)
if lang == LANGUAGE_DEFAULT:
lang_default = lang_data # Keep for later languages
###########################################################################
# Fix up the sets_db.json file
# Place entries in alphabetical order
###########################################################################
lang_file = "sets_db.json"
set_data = get_json_data(os.path.join(card_db_dir, lang_file))
write_data(set_data, os.path.join(output_dir, lang_file))
print("Sets:")
print(set(set_data))
print()
###########################################################################
# Fix up all the xx/sets_xx.json files
# Place entries in alphabetical order
# If entries don't exist:
# If the default language, set from information in the "sets_db.json" file,
# If not the default language, set based on information from the default language.
###########################################################################
for lang in languages:
lang_file = "sets_" + lang + ".json"
fname = os.path.join(card_db_dir, lang, lang_file)
if os.path.isfile(fname):
lang_set_data = get_json_data(fname)
else:
lang_set_data = {}
for s in sorted(set_data):
if s not in lang_set_data:
lang_set_data[s] = {}
if lang == LANGUAGE_DEFAULT:
lang_set_data[s]["set_name"] = s.title()
lang_set_data[s]["text_icon"] = set_data[s]["text_icon"]
if "short_name" in set_data[s]:
lang_set_data[s]["short_name"] = set_data[s]["short_name"]
if "set_text" in set_data[s]:
lang_set_data[s]["set_text"] = set_data[s]["set_text"]
else:
lang_set_data[s]["set_name"] = lang_default[s]["set_name"]
lang_set_data[s]["text_icon"] = lang_default[s]["text_icon"]
if "short_name" in lang_default[s]:
lang_set_data[s]["short_name"] = lang_default[s]["short_name"]
if "set_text" in lang_default[s]:
lang_set_data[s]["set_text"] = lang_default[s]["set_text"]
else:
if lang != LANGUAGE_DEFAULT:
for x in lang_default[s]:
if x not in lang_set_data[s] and x != "used":
lang_set_data[s][x] = lang_default[s][x]
if lang == LANGUAGE_DEFAULT:
lang_default = lang_set_data # Keep for later languages
write_data(lang_set_data, os.path.join(output_dir, lang, lang_file))
###########################################################################
# bonuses_xx files
###########################################################################
for lang in languages:
# Special case for xx. Reseed from default language
fromLanguage = lang
if lang == LANGUAGE_XX:
fromLanguage = LANGUAGE_DEFAULT
copyfile(
os.path.join(
card_db_dir, fromLanguage, "bonuses_" + fromLanguage + ".json"
),
os.path.join(output_dir, lang, "bonuses_" + lang + ".json"),
)
###########################################################################
# translation.txt
###########################################################################
copyfile(
os.path.join(card_db_dir, "translation.md"),
os.path.join(output_dir, "translation.md"),
)
# Since xx is the starting point for new translations,
# make sure xx has the latest copy of translation.txt
copyfile(
os.path.join(card_db_dir, LANGUAGE_XX, "translation.txt"),
os.path.join(output_dir, LANGUAGE_XX, "translation.txt"),
)
def run():
parser = argparse.ArgumentParser()
parser.add_argument(
"card_db_dir",
help="directory of card data (usually card_db_src at the top level of the distribution)",
)
parser.add_argument(
"output_dir", help="directory for output data (usually src/domdiv/card_db)"
)
args = parser.parse_args()
main(args.card_db_dir, args.output_dir)
if __name__ == "__main__":
run()