#!/usr/bin/env python #encoding: utf8 """ wmlunits -- tool to output information on all units in HTML Run without arguments to see usage. """ # Makes things faster on 32-bit systems try: import psyco; psyco.full() except ImportError: pass import sys, os, re, glob, shutil, copy, urllib2, gc import wesnoth.wmldata as wmldata import wesnoth.wmlparser as wmlparser import wesnoth.wmltools as wmltools sys.path.append(".") import unit_tree.helpers as helpers import unit_tree.animations as animations html_header = ''' '''.strip() html_footer = ''' '''.strip() class GroupByRace: def __init__(self, wesnoth, campaign): self.wesnoth = wesnoth self.campaign = campaign def unitfilter(self, unit): if not self.campaign: return True return unit.campaign == self.campaign def groups(self, unit): return [unit.race] def group_name(self, group): if not group: return "None" return group.get_text_val("plural_name") class GroupByFaction: def __init__(self, wesnoth, era): self.wesnoth = wesnoth self.era = era def unitfilter(self, unit): return self.era in unit.eras def groups(self, unit): return [x for x in unit.factions if x[0] == self.era] def group_name(self, group): era = self.wesnoth.era_lookup[group[0]] faction = era.faction_lookup[group[1]] name = faction.get_text_val("name") name = name[name.rfind("=") + 1:] name = era.get_text_val("name") + " / " + name return name class HTMLOutput: def __init__(self, isocode, output, campaign, wesnoth, verbose = False): self.isocode = isocode self.output = output self.campaign = campaign self.verbose = verbose self.target = "index.html" self.wesnoth = wesnoth self.forest = None def get_translation(self, textdomain, key): return self.wesnoth.parser.translations.get(textdomain, self.isocode, key, key) def analyze_units(self, grouper): """ This takes all units belonging to a campaign, then groups them either by race or faction, and creates an advancements tree out of it. """ # Build an advancement tree forest of all units. forest = self.forest = helpers.UnitForest() units_added = {} for uid, u in self.wesnoth.unit_lookup.items(): if grouper.unitfilter(u): forest.add_node(helpers.UnitNode(u)) units_added[uid] = u # Always add any child units, even if they have been filtered out.. while units_added: new_units_added = {} for uid, u in units_added.items(): for auid in u.advance: if not auid in units_added: try: au = self.wesnoth.unit_lookup[auid] except KeyError: sys.stderr.write( "Warning: Unit %s not found as advancement of %s\n" % (auid, uid)) continue forest.add_node(helpers.UnitNode(au)) new_units_added[auid] = au units_added = new_units_added forest.update() # Partition trees by race/faction of first unit. groups = {} breadth = 0 for tree in forest.trees.values(): u = tree.unit ugroups = grouper.groups(u) for group in ugroups: groups[group] = groups.get(group, []) + [tree] breadth += tree.breadth thelist = groups.keys() thelist.sort() rows_count = breadth + len(thelist) # Create empty grid. rows = [] for j in range(rows_count): column = [] for i in range(6): column.append((1, 1, None)) rows.append(column) # Sort advancement trees by name of first unit and place into the grid. def by_name(t1, t2): u1 = t1.unit u2 = t2.unit u1name = u1.get_text_val("name") u2name = u2.get_text_val("name") return cmp(u1name, u2name) def grid_place(nodes, x): nodes.sort(by_name) for node in nodes: level = node.unit.level rows[x][level] = (1, node.breadth, node) for i in range(1, node.breadth): rows[x + i][level] = (0, 0, node) grid_place(node.children, x) x += node.breadth return x x = 0 for group in thelist: node = helpers.GroupNode(group) node.name = grouper.group_name(group) rows[x][0] = (6, 1, node) for i in range(1, 6): rows[x][i] = (0, 0, None) nodes = groups[group] x += 1 x = grid_place(nodes, x) self.unitgrid = rows def write_navbar(self): def write(x): self.output.write(x) def _(x): return self.get_translation("wesnoth", x) languages = find_languages() langlist = languages.keys() langlist.sort() write("""
Wesnoth logo
Wesnoth Units database
""") write("") write("\n") def abbrev(name): abbrev = name[0] word_seperators = [" ", "_", "+", "(", ")"] for i in range(1, len(name)): if name[i] in ["+", "(", ")"] or name[i - 1] in word_seperators and name[i] not in word_seperators: abbrev += name[i] return abbrev # Campaigns x = _("TitleScreen button^Campaign") write("\n") write("\n") # Eras write("\n") x = _("Era") write("\n") write("\n") # Races if self.campaign == "mainline": write("\n") x = _("Race") write("\n") write("\n") # Languages write("\n") x = _("Language") write("\n") write("\n") write("
%s
" % x) cids = [[], []] for cid in self.wesnoth.campaign_lookup.keys(): if cid in self.wesnoth.is_mainline_campaign: cids[0].append(cid) else: cids[1].append(cid) for i in range(2): cnames = cids[i] cnames.sort() for cname in cnames: lang = self.isocode if cname == "mainline": campname = self.get_translation("wesnoth", "Multiplayer") campabbrev = campname else: campname = self.wesnoth.campaign_lookup[cname].get_text_val("name") if not campname: campname = cname campabbrev = self.wesnoth.campaign_lookup[cname].get_text_val("abbrev") if not campabbrev: campabbrev = abbrev(campname) write(" %s
\n" % ( campname, lang, cname, campabbrev)) if i == 0 and cids[1]: write("-
\n") write("
%s
" % x) eids = [[], []] for eid in self.wesnoth.era_lookup.keys(): if eid in self.wesnoth.is_mainline_era: eids[0].append(eid) else: eids[1].append(eid) for i in range(2): eranames = [(self.wesnoth.era_lookup[eid].get_text_val("name"), eid) for eid in eids[i]] eranames.sort() for eraname, eid in eranames: write(" %s
" % ( eraname, lang, eid, abbrev(eraname))) if i == 0 and eids[1]: write("-
\n") write("
%s
" % x) write("%s
\n" % ( self.get_translation("wesnoth-editor", "all"))) racenames = {} for u in self.wesnoth.unit_lookup.values(): if u.campaign != self.campaign: continue race = u.race racename = race.get_text_val("plural_name") racenames[racename] = race.get_text_val("id") racenames = racenames.items() racenames.sort() for racename, rid in racenames: write(" %s
" % ( racename, racename)) write("
%s
" % x) for lang in langlist: labb = lang underscore = labb.find("_") if underscore > 0: labb = labb[:underscore] write(" %s
\n" % ( languages[lang], lang, self.target, labb)) write("
\n") def pic(self, u, x): image = self.wesnoth.get_unit_value(x, "image") if not image: if x.name == "female": baseunit = self.wesnoth.get_base_unit(u) if baseunit: female = baseunit.get_first("female") return self.pic(u, female) sys.stderr.write( "Warning: Missing image for unit %s(%s).\n" % ( u.get_text_val("id"), x.name)) return None picname = image_collector.add_image(u.campaign, image) image = os.path.join("../pics", picname) return image def get_abilities(self, u): anames = [] already = {} for abilities in u.get_all("abilities"): try: c = abilities.children() except AttributeError: c = [] for ability in c: id = ability.get_text_val("id") if id in already: continue already[id] = True name = ability.get_text_val("name") if not name: name = id if not name: name = ability.name anames.append(name) return anames def get_recursive_attacks(self, this_unit): def copy_attributes(copy_from, copy_to): for c in copy_from.children(): if isinstance(c, wmldata.DataText): copy_to.set_text_val(c.name, c.data) # Use attacks of base_units as base, if we have one. base_unit = self.wesnoth.get_base_unit(this_unit) attacks = [] if base_unit: attacks = copy.deepcopy(self.get_recursive_attacks(base_unit)) base_attacks_count = len(attacks) for i, attack in enumerate(this_unit.get_all("attack")): # Attack merging is order based. if i < base_attacks_count: copy_attributes(attack, attacks[i]) else: attacks.append(attack) return attacks def write_units(self): def write(x): self.output.write(x) def _(x): return self.get_translation("wesnoth", x) rows = self.unitgrid write("\n") write("") for i in range(6): write("" % i) write("") pic = image_collector.add_image("mainline", "../../../images/misc/leader-crown.png") crownimage = os.path.join("../pics", pic) ms = None for row in range(len(rows)): write("\n") for column in range(6): hspan, vspan, un = rows[row][column] if vspan: attributes = "" if hspan == 1 and vspan == 1: pass elif hspan == 1: attributes += " rowspan=%d" % vspan elif vspan == 1: attributes += " colspan=%d" % hspan if un and isinstance(un, helpers.GroupNode): # Find the current multiplayer side so we can show the # little crowns.. ms = None try: eid, fid = un.data era = self.wesnoth.era_lookup[eid] ms = era.faction_lookup[fid] except TypeError: pass racename = un.name attributes += " class=\"raceheader\"" write("" % attributes) write("%s" % (racename, racename)) write("\n") elif un: u = un.unit attributes += " class=\"unitcell\"" write("" % attributes) uid = u.get_text_val("id") name = self.wesnoth.get_unit_value(u, "name") cost = self.wesnoth.get_unit_value(u, "cost") hp = self.wesnoth.get_unit_value(u, "hitpoints") mp = self.wesnoth.get_unit_value(u, "movement") xp = self.wesnoth.get_unit_value(u, "experience") level = self.wesnoth.get_unit_value(u, "level") crown = "" if ms: if un.id in ms.units: crown = u" ♟" if un.id in ms.is_leader: crown = u" ♚" link = "../%s/%s.html" % (self.isocode, uid) write("
%s" % ( link, uid, u"ℹ")) write("
") write("
L%s%s
" % (level, crown)) write("%s
" % (link, name)) write('\n') write("
") write("%s%s
" % ( self.get_translation("wesnoth", "Cost: "), cost)) write("%s%s
" % ( self.get_translation("wesnoth", "HP: "), hp)) write("%s%s
" % ( self.get_translation("wesnoth", "MP: "), mp)) write("%s%s
" % ( self.get_translation("wesnoth", "XP: "), xp)) # Write info about abilities. anames = self.get_abilities(u) if anames: write("\n
") write(", ".join(anames)) write("
") # Write info about attacks. write("\n
") attacks = self.get_recursive_attacks(u) for attack in attacks: n = attack.get_text_val("number") x = attack.get_text_val("damage") x = "%s - %s" % (x, n) write("%s " % x) r = attack.get_text_val("range") t = attack.get_text_val("type") write("%s (%s)" % (_(r), _(t))) s = [] specials = attack.get_first("specials") if specials: for special in specials.children(): sname = special.get_text_val("name") if sname: s.append(sname) s = ", ".join(s) if s: write(" (%s)" % s) write("
") write("
") write("
") write("\n") else: write("
") write("\n") write("
\n") def write_units_tree(self, grouper, title): self.output.write(html_header % {"path" : "../"}) self.analyze_units(grouper) self.write_navbar() self.output.write("

%s

" % title) self.write_units() self.output.write(html_footer) def write_unit_report(self, output, unit): def write(x): self.output.write(x) def _(x): return self.get_translation("wesnoth", x) def find_attr(what, key): if unit.movetype: mtx = unit.movetype.get_first(what) mty = None if mtx: mty = mtx.get_text_val(key) x = unit.get_first(what) y = None if x: y = x.get_text_val(key) if y: return True, y if unit.movetype and mty != None: return False, mty return False, "-" self.output = output write(html_header % {"path" : "../"}) self.write_navbar() # Write unit name, picture and description. uid = unit.get_text_val("id") uname = self.wesnoth.get_unit_value(unit, "name") display_name = uname female = unit.get_first("female") if female: fname = female.get_text_val("name") if fname and fname != uname: display_name += "
" + fname write("

%s

\n" % display_name) write('
') if female: mimage = self.pic(unit, unit) fimage = self.pic(unit, female) if not fimage: fimage = mimage write('(image)\n' % mimage) write('(image)\n' % fimage) else: image = self.pic(unit, unit) write('(image)\n' % image) write('
\n') description = self.wesnoth.get_unit_value(unit, "description") # TODO: what is unit_description? if not description: description = unit.get_text_val("unit_description") if not description: description = "-" write("

%s

\n" % __import__('re').sub("\n","\n
", description) ) # Base info. hp = self.wesnoth.get_unit_value(unit, "hitpoints") mp = self.wesnoth.get_unit_value(unit, "movement") xp = self.wesnoth.get_unit_value(unit, "experience") level = self.wesnoth.get_unit_value(unit, "level") alignment = self.wesnoth.get_unit_value(unit, "alignment") write("\n") write("\n") write("\n") write("\n") write("\n") write("\n") for val, text in [ ("cost", _("Cost: ")), ("hitpoints", _("HP: ")), ("movement", _("Movement") + ": "), ("experience", _("XP: ")), ("level", _("Level") + ": "), ("alignment", _("Alignment: ")), ("id", "ID")]: write("\n") write("" % text) x = self.wesnoth.get_unit_value(unit, val) if val == "alignment": x = self.get_translation("wesnoth", x) write("" % x) write("\n") # Write info about abilities. anames = self.get_abilities(unit) write("\n") write("" % _("Abilities: ")) write("") write("\n") write("
%s" % _("Advances from: ")) write("\n") for pid in self.forest.get_parents(uid): punit = self.wesnoth.unit_lookup[pid] if unit.campaign == "mainline" and punit.campaign != "mainline": continue link = "../%s/%s.html" % (self.isocode, pid) name = self.wesnoth.get_unit_value(punit, "name") write("\n%s" % (link, name)) write("
%s" % self.get_translation("wesnoth", "Advances to: ")) write("\n") for cid in self.forest.get_children(uid): link = "../%s/%s.html" % (self.isocode, cid) try: cunit = self.wesnoth.unit_lookup[cid] if unit.campaign == "mainline" and cunit.campaign != "mainline": continue name = self.wesnoth.get_unit_value(cunit, "name") except KeyError: sys.stderr.write("Warning: Unit %s not found.\n" % cid) name = cid if unit.campaign == "mainline": continue write("\n%s" % (link, name)) write("
%s%s
%s" + (", ".join(anames)) + "
\n") # Write info about attacks. write("\n") attacks = self.get_recursive_attacks(unit) for attack in attacks: write("") aid = attack.get_text_val("name") aname = attack.get_text_val("description") icon = attack.get_text_val("icon") if not icon: icon = "attacks/%s.png" % aid picname, error = image_collector.add_image_check(unit.campaign, icon) if error: sys.stderr.write("Error: No attack icon '%s' found for '%s'.\n" % ( icon, uid)) icon = os.path.join("../pics", picname) write("" % icon) write("" % self.get_translation("wesnoth", t)) n = attack.get_text_val("number") x = attack.get_text_val("damage") x = "%s - %s" % (x, n) write("" % self.get_translation("wesnoth", r)) s = [] specials = attack.get_first("specials") if specials: for special in specials.children(): sname = special.get_text_val("name") if sname: s.append(sname) else: sys.stderr.write( "Warning: Weapon special %s has no name for %s.\n" % ( special.name, uid)) s = "
".join(s) write("" % s) write("") write("
\"(image)\"/%s" % aname) t = attack.get_text_val("type") write("
%s
%s" % x) r = attack.get_text_val("range") write("
%s
%s
\n") # Write info about resistances. resistances = [ "blade", "pierce", "impact", "fire", "cold", "arcane"] write("\n") write("\n") write("\n" % self.get_translation("wesnoth", "Resistances: ")) write("\n") for rid in resistances: special, r = find_attr("resistance", rid) if r == "-": r = 100 try: r = "%d%%" % (100 - int(r)) except ValueError: sys.stderr.write("Warning: Invalid resistance %s for %s.\n" % ( r, uid)) rcell = "td" if special: rcell += ' class="special"' write("\n") write("\n" % ( self.get_translation("wesnoth", rid), r)) write("\n") write("
%s
%s%s
\n") # Write info about movement costs and terrain defense. write("\n") write("\n") write("\n" % ( _("Terrain"), _("Movement Cost"), _("Defense") )) write("\n") terrains = self.wesnoth.terrain_lookup terrainlist = [] for tstring, t in terrains.items(): tid = t.get_text_val("id") if tid in ["off_map", "fog", "shroud"]: continue if t.get_first("aliasof"): continue name = t.get_text_val("name") terrainlist.append((name, tid)) terrainlist.sort() for tname, tid in terrainlist: not_from_race, c = find_attr("movement_costs", tid) ccell = "td" if c == "99": ccell += ' class="grayed"' dcell = "td" not_from_race, d = find_attr("defense", tid) if d == "-": d = 100 try: d = "%d%%" % (100 - int(d)) except ValueError: sys.stderr.write("Warning: Invalid defense %s for %s.\n" % ( d, uid)) write("\n") write("<%s>%s<%s>%s\n" % ( tname, ccell, c, dcell, d)) write("\n") write("
%s%s%s
%s
\n") write(html_footer) languages_found = {} def find_languages(): """ Returns a dictionary mapping isocodes to languages. """ global languages if languages_found: return languages_found parser = wmlparser.Parser(datadir) WML = wmldata.DataSub("WML") parser.parse_text("{languages}\n") parser.parse_top(WML) for locale in WML.get_all("locale"): isocode = locale.get_text_val("locale") name = locale.get_text_val("name") languages_found[isocode] = name return languages_found class MyFile: """ I don't understand why this is needed.. """ def __init__(self, filename, mode): self.f = open(filename, mode) def write(self, x): x = x.encode("utf8") self.f.write(x) def close(self): self.f.close() def generate_campaign_report(out_path, isocode, campaign, wesnoth): print "Generating report for %s_%s." % (isocode, campaign) path = os.path.join(out_path, isocode ) if not os.path.isdir(path): os.mkdir(path) output = MyFile(os.path.join(path, "%s.html" % campaign), "w") html = HTMLOutput(isocode, output, campaign, wesnoth) html.target = "%s.html" % campaign grouper = GroupByRace(wesnoth, campaign) if campaign == "mainline": title = html.get_translation("wesnoth", "Multiplayer") else: title = wesnoth.campaign_lookup[campaign].get_text_val("name") if not title: title = campaign html.write_units_tree(grouper, title) def generate_era_report(out_path, isocode, eid, wesnoth): print "Generating report for %s_%s." % (isocode, eid) path = os.path.join(out_path, isocode) if not os.path.isdir(path): os.mkdir(path) era = wesnoth.era_lookup[eid] ename = era.get_text_val("name") output = MyFile(os.path.join(path, "%s.html" % eid), "w") html = HTMLOutput(isocode, output, eid, wesnoth) html.target = "%s.html" % eid grouper = GroupByFaction(wesnoth, eid) title = ename html.write_units_tree(grouper, title) def generate_single_unit_reports(out_path, isocode, wesnoth): odir = os.path.join(out_path, isocode) if not os.path.isdir(odir): os.mkdir(odir) html = HTMLOutput(isocode, None, "units", wesnoth) grouper = GroupByRace(wesnoth, None) html.analyze_units(grouper) for uid, unit in wesnoth.unit_lookup.items(): output = MyFile(os.path.join(odir, "%s.html" % uid), "w") html.target = "%s.html" % uid html.write_unit_report(output, unit) def write_index(out_path): output = MyFile(os.path.join(out_path, "index.html"), "w") output.write(""" Redirecting to Wesnoth units database... """) def copy_images(): if not options.nocopy: print "Copying files." image_collector.copy_and_color_images(options.output) shutil.copy2(os.path.join(datadir, "tools/unit_tree/style.css"), options.output) for grab in [ "http://www.wesnoth.org/mw/skins/glamdrol/headerbg.jpg", "http://www.wesnoth.org/mw/skins/glamdrol/wesnoth-logo.jpg", "http://www.wesnoth.org/mw/skins/glamdrol/navbg.png"]: local = os.path.join(options.output, grab[grab.rfind("/") + 1:]) if not os.path.exists(local): print "Fetching", grab url = urllib2.urlopen(grab) file(local, "w").write(url.read()) def output(isocode): """ We output: * All mainline units sorted by race * Each mainline era's units sorted by faction * Each mainline campaign's units sorted by race * Each addon era's units sorted by faction * Each addon campaign's units sorted by race """ print "WML parser language reset to %s." % isocode stuff = helpers.WesnothList(isocode, datadir, userdir, transdir) # Parse some stuff we may need. print "Parsing terrains ...", n = stuff.add_terrains() print "%d terrains found." % n print "Parsing mainline eras ...", n = stuff.add_mainline_eras() print "%d mainline eras found." % n stuff.is_mainline_era = {} for c in stuff.era_lookup.keys(): stuff.is_mainline_era[c] = True print "Parsing mainline campaigns ...", n = stuff.add_mainline_campaigns() print "%d mainline campaigns found." % n stuff.is_mainline_campaign = {} for c in stuff.campaign_lookup.keys(): stuff.is_mainline_campaign[c] = True # Parse all unit data # This reads in units.cfg, giving us all the mainline units (and races). print "Parsing mainline units ...", sys.stdout.flush() WML = stuff.parser.parse("{core/units.cfg}") n = stuff.add_units(WML, "mainline") print n, "mainline units found." # Now we read each mainline campaign in turn to get its units. cnames = stuff.campaign_lookup.keys() for cname in cnames: print "Parsing %s units ..." % cname, sys.stdout.flush() campaign = stuff.campaign_lookup[cname] define = campaign.get_text_val("define") WML = stuff.parser.parse(""" #define %s\n#enddef {campaigns}""" % define, ignore_macros = lambda x: x.find("/scenarios") == -1) n = stuff.add_units(WML, cname) image_collector.add_binary_pathes_from_WML(cname, WML) print n, "units found." sys.stderr.flush() if userdir: print "Parsing addons ...", sys.stdout.flush() n = stuff.add_addons(image_collector) print "%d units found." % n # Now we read each addon campaign in turn to get its units. cnames = stuff.campaign_lookup.keys() for cname in cnames: if cname in stuff.is_mainline_campaign: continue campaign = stuff.campaign_lookup[cname] print "Parsing %s units ..." % cname, sys.stdout.flush() define = campaign.get_text_val("define") WML = stuff.parser.parse(""" #define %s\n#enddef {~add-ons}""" % define, ignore_macros = lambda x: x.find("/scenarios") == -1) n = stuff.add_units(WML, cname) image_collector.add_binary_pathes_from_WML(cname, WML) print n, "units found." stuff.find_unit_factions() # Report generation if not os.path.isdir(options.output): os.mkdir(options.output) write_index(options.output) campaigns = stuff.campaign_lookup.keys() generate_campaign_report(options.output, isocode, "mainline", stuff) for campaign in campaigns: generate_campaign_report(options.output, isocode, campaign, stuff) for eid in stuff.era_lookup.keys(): generate_era_report(options.output, isocode, eid, stuff) # Single unit reports. generate_single_unit_reports(options.output, isocode, stuff) return stuff if __name__ == '__main__': # We change the process name to "wmlunits" try: import ctypes libc = ctypes.CDLL("libc.so.6") libc.prctl(15, "wmlunits", 0, 0, 0) except: # oh well... pass global options global image_collector import optparse gc.disable() op = optparse.OptionParser() op.add_option("-l", "--language", default = "all", help = "Specify a language.") op.add_option("-o", "--output", help = "Specify output directory.") op.add_option("-n", "--nocopy", action = "store_true", help = "No copying of files.") op.add_option("-d", "--datadir", help = "Specify Wesnoth's data to use. Default is /data.") op.add_option("-u", "--userdir", help = "Specify user data dir to use, which is automatically scanned "+\ "for addons. For example -u ~/.wesnoth/data") op.add_option("-t", "--transdir", help = "Specify the directory which has a po subfolder for translations. "+\ "(Defaults to the current directory.)") options, args = op.parse_args() if not options.output: op.print_help() sys.exit(-1) options.output = os.path.expanduser(options.output) if not options.datadir: #wmltools.pop_to_top("wmlunits") datadir = os.getcwd() + "/data" else: datadir = options.datadir userdir = options.userdir if options.transdir: transdir = options.transdir else: transdir = os.getcwd() if options.language == "all": languages = find_languages().keys() languages.sort() else: languages = [options.language] image_collector = helpers.ImageCollector(datadir, userdir) once_without_translation = False for isocode in languages: wesnoth = output(isocode) if not once_without_translation: copy_images() once_without_translation = True for u in wesnoth.unit_lookup.values(): r = u.race if r: r = r.get_text_val("id", "none") else: r = "none" # Store rid and id for each units so we have it easier u.rid = r u.id = u.get_text_val("id") # Write a list with all unit ids, just for fun. uids = wesnoth.unit_lookup.keys() def by_race(u1, u2): r = cmp(wesnoth.unit_lookup[u1].rid, wesnoth.unit_lookup[u2].rid) if r == 0: r = cmp(u1, u2) return r uids.sort(by_race) race = None f = MyFile(os.path.join(options.output, "uids.html"), "w") f.write("") for uid in uids: u = wesnoth.unit_lookup[uid] if u.rid != race: if race != None: f.write("") f.write("

%s

\n" % u.rid) f.write("") f.write("") f.close() # Write animation statistics f = MyFile(os.path.join(options.output, "animations.html"), "w") animations.write_table(f, wesnoth) f.close() # Kill all references, this makes sure the reference counting # kicks in and throws away all data for the previous # translation - saving a bit of memory since otherwise two # translations would be kept at a time. wesnoth = None # We run a GC collection here, this ensures that also data # which still are referenced but not reachable (yes, yes, # this shouldn't happen) are freed. gc.collect()