Merge branch 'staging/wesmere-wmlunits'

This commit is contained in:
Ignacio R. Morelle 2017-08-12 02:27:18 -04:00
commit 086de4a897
6 changed files with 1089 additions and 1145 deletions

View file

@ -1,19 +1,21 @@
"""
Various helpers for use by the wmlunits tool.
"""
import sys, os, re, glob, shutil, copy, urllib.request, urllib.error, urllib.parse, subprocess
import sys, os, re, glob, shutil, copy, subprocess
import wesnoth.wmlparser3 as wmlparser3
def get_datadir(wesnoth_exe):
p = subprocess.Popen([wesnoth_exe, "--path"],
stdout = subprocess.PIPE, stderr = subprocess.PIPE)
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
out, err = p.communicate()
return out.strip()
def get_userdir(wesnoth_exe):
p = subprocess.Popen([wesnoth_exe, "--config-path"],
stdout = subprocess.PIPE, stderr = subprocess.PIPE)
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
out, err = p.communicate()
return out.strip()
@ -35,50 +37,52 @@ class ImageCollector:
self.images_by_ipath = {}
self.binary_paths_per_addon = {}
self.datadir = datadir
if not self.datadir:
self.datadir = get_datadir(wesnoth_exe)
self.userdir = userdir
if not self.datadir: self.datadir = get_datadir(wesnoth_exe)
if not self.userdir: self.userdir = get_userdir(wesnoth_exe)
if not self.userdir:
self.userdir = get_userdir(wesnoth_exe)
def add_binary_paths_from_WML(self, addon, WML):
for binpath in WML.get_all(tag = "binary_path"):
for binpath in WML.get_all(tag="binary_path"):
path = binpath.get_text_val("path")
if addon not in self.binary_paths_per_addon:
self.binary_paths_per_addon[addon] = []
self.binary_paths_per_addon[addon].append(path)
def _find_image(self, addon, name):
tilde = name.find("~")
if tilde >= 0:
name = name[:tilde]
bases = [os.path.join(self.datadir, "data/core/images")]
binpaths = self.binary_paths_per_addon.get(addon, [])[:]
binpaths.reverse()
for x in binpaths:
for path in binpaths:
for idir in ["images", "images/units"]:
bases.append(os.path.join(self.datadir, x, idir))
bases.append(os.path.join(self.userdir, x, idir))
bases.append(os.path.join(self.datadir, path, idir))
bases.append(os.path.join(self.userdir, path, idir))
bases = [os.path.join("%s" % base, name) for base in bases]
for ipath in bases:
if os.path.exists(ipath): return ipath, bases
if os.path.exists(ipath):
return ipath, bases
return None, bases
def add_image_check(self, addon, name, no_tc = False):
def add_image_check(self, addon, name, no_tc=False):
if (addon, name) in self.images_by_addon_name:
image = self.images_by_addon_name[(addon, name)]
if addon not in image.addons: image.addons.add(addon)
if addon not in image.addons:
image.addons.add(addon)
return image
ipath, bases = self._find_image(addon, name)
if ipath in self.images_by_ipath:
image = self.images_by_ipath[ipath]
if addon not in image.addons: image.addons.add(addon)
if addon not in image.addons:
image.addons.add(addon)
return image
def make_name(x):
x = x.strip("./ ")
d = options.config_dir.strip("./ ")
@ -90,8 +94,10 @@ class ImageCollector:
x = x.strip("./ ")
y = ""
for c in x:
if c == "/": c = "$"
elif not c.isalnum() and c not in ".+-()[]{}": c = "_"
if c == "/":
c = "$"
elif not c.isalnum() and c not in ".+-()[]{}":
c = "_"
y += c
return y
@ -102,14 +108,14 @@ class ImageCollector:
image = Image(id_name, ipath, bases, no_tc)
image.addons.add(addon)
self.images_by_addon_name[(addon, name)] = image
if ipath:
self.images_by_ipath[ipath] = image
return image
def add_image(self, addon, path, no_tc = False):
def add_image(self, addon, path, no_tc=False):
image = self.add_image_check(addon, path, no_tc)
return image.id_name
@ -122,7 +128,7 @@ class ImageCollector:
pass
no_tc = image.no_tc
ipath = os.path.normpath(image.ipath)
cdir = os.path.normpath(options.config_dir + "/data/add-ons")
if ipath.startswith(cdir):
@ -134,7 +140,7 @@ class ImageCollector:
# We assume TeamColorizer is in the same directory as the
# helpers.py currently executing.
command = os.path.join(os.path.dirname(__file__),
"TeamColorizer")
"TeamColorizer")
p = subprocess.Popen([command, ipath, opath])
p.wait()
else:
@ -160,9 +166,7 @@ class WesnothList:
self.movetype_lookup = {}
self.era_lookup = {}
self.campaign_lookup = {}
self.parser = wmlparser3.Parser(wesnoth_exe, config_dir,
data_dir)
self.parser = wmlparser3.Parser(wesnoth_exe, config_dir, data_dir)
def add_terrains(self):
"""
@ -171,20 +175,21 @@ class WesnothList:
self.parser.parse_text("{core/terrain.cfg}\n")
n = 0
for terrain in self.parser.get_all(tag = "terrain_type"):
for terrain in self.parser.get_all(tag="terrain_type"):
tstring = terrain.get_text_val("string")
self.terrain_lookup[tstring] = terrain
n += 1
return n
def add_languages(self, languages):
"""
Returns a dictionary mapping isocodes to languages.
"""
self.languages_found = {}
parser = wmlparser3.Parser(options.wesnoth, options.config_dir,
options.data_dir)
parser = wmlparser3.Parser(options.wesnoth,
options.config_dir,
options.data_dir)
parser.parse_text("{languages}")
for locale in parser.get_all(tag="locale"):
@ -199,12 +204,14 @@ class WesnothList:
For an era, list all factions and units in it.
"""
eid = era.get_text_val("id")
if not eid: return
if not eid:
return
self.era_lookup[eid] = era
era.faction_lookup = {}
for multiplayer_side in era.get_all(tag = "multiplayer_side"):
for multiplayer_side in era.get_all(tag="multiplayer_side"):
fid = multiplayer_side.get_text_val("id")
if fid == "Random": continue
if fid == "Random":
continue
era.faction_lookup[fid] = multiplayer_side
recruit = multiplayer_side.get_text_val("recruit", "").strip()
leader = multiplayer_side.get_text_val("leader", "").strip()
@ -242,7 +249,7 @@ class WesnothList:
self.parser.parse_text("{multiplayer/eras.cfg}")
n = 0
for era in self.parser.get_all(tag = "era"):
for era in self.parser.get_all(tag="era"):
self.add_era(era)
n += 1
return n
@ -253,29 +260,29 @@ class WesnothList:
we reference them everywhere by this id, and here can add them all to
one big collection.
"""
addunits = self.parser.get_all(tag = "units")
addunits += self.parser.get_all(tag = "+units")
if not addunits: return 0
addunits = self.parser.get_all(tag="units")
addunits += self.parser.get_all(tag="+units")
if not addunits:
return 0
def getall(oftype):
r = []
r = []
res = []
for units in addunits:
r += units.get_all(tag = oftype)
return r
res += units.get_all(tag=oftype)
return res
# Find all unit types.
newunits = getall("unit_type") + getall("unit")
for unit in newunits:
uid = unit.get_text_val("id")
unit.id = uid
if unit.get_text_val("do_not_list", "no") != "no" or\
if unit.get_text_val("do_not_list", "no") not in ["no", "false"] or \
unit.get_text_val("hide_help", "no") not in ["no", "false"]:
unit.hidden = True
unit.hidden = True
else:
unit.hidden = False
if uid in self.unit_lookup:
unit = self.unit_lookup[uid]
# TODO: We might want to compare the two units
@ -283,16 +290,16 @@ class WesnothList:
# to do something clever like rename it...
else:
self.unit_lookup[uid] = unit
if not hasattr(unit, "campaigns"): unit.campaigns = []
if not hasattr(unit, "campaigns"):
unit.campaigns = []
unit.campaigns.append(campaign)
# Find all races.
newraces = getall("race")
for race in newraces:
rid = race.get_text_val("id")
if rid == None:
if rid is None:
rid = race.get_text_val("name")
self.race_lookup[rid] = race
# Find all movetypes.
@ -314,8 +321,10 @@ class WesnothList:
error_message("Warning: No race \"%s\" found (%s).\n" % (
race, unit.get_text_val("id")))
movetype = self.get_unit_value(unit, "movement_type")
try: unit.movetype = self.movetype_lookup[movetype]
except KeyError: unit.movetype = None
try:
unit.movetype = self.movetype_lookup[movetype]
except KeyError:
unit.movetype = None
unit.advance = []
advanceto = unit.get_text_val("advances_to")
@ -326,7 +335,6 @@ class WesnothList:
for advance in advanceto.split(","):
auid = advance.strip()
if auid: unit.advance.append(auid)
# level
try:
level = int(self.get_unit_value(unit, "level"))
@ -343,10 +351,9 @@ class WesnothList:
"""
Once all units have been added, do some checking.
"""
# handle advancefrom tags
for uid, unit in list(self.unit_lookup.items()):
for advancefrom in unit.get_all(tag = "advancefrom"):
for advancefrom in unit.get_all(tag="advancefrom"):
fromid = advancefrom.get_text_val("unit")
if fromid:
try:
@ -363,7 +370,6 @@ class WesnothList:
for unit in list(self.unit_lookup.values()):
unit.factions = []
unit.eras = []
for eid, era in list(self.era_lookup.items()):
for fid, multiplayer_side in list(era.faction_lookup.items()):
for uid in multiplayer_side.units:
@ -372,15 +378,14 @@ class WesnothList:
except KeyError:
error_message(
("Error: Era '%s' faction '%s' references " +
"non-existant unit id '%s'!\n") % (
eid,
fid,
str(uid)))
"non-existant unit id '%s'!\n") % (
eid,
fid,
str(uid)))
continue
if not eid in unit.eras:
unit.eras.append(eid)
unit.factions.append((eid, fid))
# as a special case, add units from this addon but with no faction
for unit in list(self.unit_lookup.values()):
if unit.campaigns[0] == self.cid:
@ -388,25 +393,25 @@ class WesnothList:
if not eid in unit.eras:
unit.eras.append(eid)
unit.factions.append((eid, None))
def get_base_unit(self, unit):
b = unit.get_all(tag = "base_unit")
b = unit.get_all(tag="base_unit")
if b:
b = b[0]
buid = b.get_text_val("id")
try: baseunit = self.unit_lookup[buid]
try:
baseunit = self.unit_lookup[buid]
except KeyError:
error_message(
"Warning: No baseunit \"%s\" for \"%s\".\n" % (
buid, unit.get_text_val("id")))
buid, unit.get_text_val("id")))
return None
return baseunit
return None
def get_unit_value(self, unit, attribute, default = None, translation = None):
def get_unit_value(self, unit, attribute, default=None, translation=None):
value = unit.get_text_val(attribute, None, translation)
if value == None:
if value is None:
baseunit = self.get_base_unit(unit)
if baseunit:
return self.get_unit_value(baseunit, attribute, default, translation)
@ -438,7 +443,8 @@ class UnitForest:
for uid, u in list(self.lookup.items()):
for cid in u.child_ids:
c = self.lookup.get(cid, None)
if not c: continue
if not c:
continue
u.children.append(c)
if not uid in c.parent_ids:
c.parent_ids.append(uid)
@ -458,7 +464,7 @@ class UnitForest:
if c.id in already:
error_message(
("Warning: Unit %s advances to unit %s in a loop.\n" %
(u.id, c.id)) +
(u.id, c.id)) +
(" Removing advancement %s.\n" % c.id))
u.children.remove(c)
for c in u.children:
@ -469,7 +475,6 @@ class UnitForest:
def update(self):
self.create_network()
self.breadth = sum([x.update_breadth() for x in list(self.trees.values())])
return self.breadth

File diff suppressed because it is too large Load diff

View file

@ -1,32 +1,80 @@
var all = new Array();
/*
* Popup menu implementation for the Wesnoth units database
*/
(function() {
var menus = [];
var visibleMenu = false;
function toggle_menu(event, id, what) {
var e = document.getElementById(id);
var show = what;
/* Since we have Javascript, disable the CSS menu trigger. */
if (event.className != "") {
event.className = "";
e.style.display = 'none';
return;
function toggleMenu(event, menu)
{
hideMenus(menu);
if (!menu.style.display || menu.style.display === "none") {
menu.style.display = "block";
visibleMenu = true;
} else {
menu.style.display = "none";
visibleMenu = false;
}
}
if (show == 0 || show == 1) return;
if (show == 2) {
if (e.style.display == 'block') show = 0;
}
if (show == 0) {
e.style.display = 'none';
}
else {
e.style.display = 'block';
all[id] = 1;
for (mid in all) {
if (mid != id) {
e = document.getElementById(mid);
e.style.display = 'none';
function hideMenus(skipMenu)
{
if (!visibleMenu) {
return;
}
for (var i = 0; i < menus.length; ++i) {
if (menus[i] !== skipMenu) {
menus[i].style.display = "none";
}
}
}
}
function isMenuBox(e)
{
return e.className.search(/\bpopupmenu\b/) != -1;
}
function isNavBar(e)
{
return e.className.search(/\bnavbar\b/) != -1;
}
window.addEventListener("load", function() {
var navItems = document.getElementsByClassName("popupcontainer");
// Set event handlers for individual menu triggers.
for (var i = 0; i < navItems.length; ++i) {
var navItem = navItems[i],
menu = navItem.getElementsByClassName("popupmenu")[0];
menus.push(menu);
var id = menu.id,
a = navItem.getElementsByClassName("popuptrigger")[0];
a.addEventListener("click", function(event) {
event.preventDefault();
event.stopPropagation();
toggleMenu(event, event.target.nextElementSibling);
}, false);
}
// Dismiss all visible menus on click outside them.
document.addEventListener("click", function(event) {
if (!visibleMenu) {
return;
}
var parent = event.target;
while(parent && !isMenuBox(parent) && !isNavBar(parent)) {
parent = parent.parentElement;
}
if (!parent || !isMenuBox(parent)) {
hideMenus();
}
}, false);
}, false);
})();

View file

@ -1,103 +1,101 @@
#!/usr/bin/env python2
import glob, os, sys, time, re
import glob
import os
import re
import sys
import time
sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
from . import html_output
def write_addon_overview(folder, addon):
out = open(os.path.join(folder, "index.html"), "w")
def w(x): out.write(x + "\n")
def w(x):
out.write(x + "\n")
name = addon["name"]
path = "../"
title = name + " Overview"
generation_note = "generated on " + time.ctime()
w(html_output.html_header % locals())
w(html_output.top_bar % locals())
title = html_output.cleantext("Build Report for " + name)
generation_note = "Last updated on " + time.ctime() + "."
w(html_output.website_header(path="../",
title=title,
classes=["wmlunits-report"]))
w('<div class="overview">')
eras = addon.get("eras", [])
w("<h2>" + name + "</h2>")
w('<h2>' + html_output.cleantext(name) + '</h2>')
if eras:
w("<h3>Eras</h3><ul>")
w('<h3>Eras</h3><ul>')
for era in eras:
epath = os.path.join("en_US", era["id"] + ".html")
w('<li><a href="' + epath + '">' + era["name"] + '</a></li>')
w("</ul>")
epath = html_output.cleanurl(os.path.join("en_US", era["id"] + ".html"))
w('<li><a href="' + epath + '">' + html_output.cleantext(era["name"], quote=False) + '</a></li>')
w('</ul>')
campaigns = addon.get("campaigns", [])
if campaigns:
w("<h3>Campaigns</h3><ul>")
w('<h3>Campaigns</h3><ul>')
for campaign in campaigns:
cpath = os.path.join("en_US", campaign["id"] + ".html")
w('<li><a href="' + cpath + '">' + campaign["name"] + '</a></li>')
w("</ul>")
w("<div>")
w('<li><a href="' + cpath + '">' + html_output.cleantext(campaign["name"], quote=False) + '</a></li>')
w('</ul>')
w('<div>')
if os.path.exists(os.path.join(folder, "error.log")):
w('<p><b>Warnings or errors were found: <a href="error.html"/>log</a></b></p>')
w('<p><a href="../overview.html">back to overview</a></p>')
w("</div>")
w('<p><b>Warnings or errors were found: <a href="error.html">log</a></b></p>')
w('<p><a href="../overview.html">Back to the full report</a></p>')
w('</div>')
w('</div> <!-- overview -->')
w(html_output.html_footer % locals())
w(html_output.website_footer())
def main(folder):
out = open(os.path.join(folder, "overview.html"), "w")
def w(x): out.write(x + "\n")
def w(x):
out.write(x + "\n")
path = ""
title = "Wesnoth Unit Database Overview"
generation_note = "generated on " + time.ctime()
w(html_output.website_header(path="",
title="Database Build Report",
classes=["wmlunits-report"]))
w('<h1>Database Build Report</h1>')
w(html_output.html_header % locals())
w(html_output.top_bar % locals())
w('<div class="overview">')
w('<table class="overview">')
w("<tr><th>")
w("Addon")
w("</th><th>")
w("Output Files")
w("</th><th>")
w("Error Log")
w("</th></tr>")
w('<thead><tr><th>Addon</th><th>Output Files</th><th>Error Log</th></tr></thead>')
w('<tbody>')
count = 0
total_n = 0
total_error_logs = 0
total_lines = 0
for f in sorted(glob.glob(os.path.join(folder, "*"))):
if not os.path.isdir(f): continue
if f.endswith("/pics"): continue
if not os.path.isdir(f):
continue
if f.endswith("/pics"):
continue
error_log = os.path.abspath(os.path.join(f, "error.log"))
error_html = os.path.abspath(os.path.join(f, "error.html"))
try:
n = len(os.listdir(os.path.join(f, "en_US")))
except OSError:
n = 0
total_n += n
name = f[len(folder):].lstrip("/")
error_name = os.path.join(name, "error.html")
w('<tr><td>')
w('<a href="' + os.path.join(name, "index.html") + '">' + name + '</a>')
w('<a href="' + html_output.cleanurl(os.path.join(name, "index.html")) + '">' + html_output.cleantext(name, quote=False) + '</a>')
w('</td><td>')
w(str(n))
w('</td><td>')
if os.path.exists(error_log):
text = open(error_log).read()
error_kind = "warnings"
if "<INTERNAL ERROR>" in text:
@ -108,19 +106,17 @@ def main(folder):
error_kind = "parse error"
elif "<TIMEOUT ERROR>" in text:
error_kind = "timeout"
source = []
def postprocess(line):
if line == "WMLError:": return ""
if line == "?": return ""
if line == "Preprocessor error:": return ""
if line.startswith("Automatically found a possible data directory"): return ""
if line.startswith("Overriding data directory with"): return ""
if line == "'SKIP_CORE' defined.": return ""
if re.match("added .* defines.", line): return ""
if line.startswith("skipped 'data/core'"): return ""
if line.startswith("preprocessing specified resource:"): return ""
if line in ("WMLError:", "?", "Preprocessor error:", "'SKIP_CORE' defined.") or \
line.startswith("Automatically found a possible data directory") or \
line.startswith("Overriding data directory with") or \
line.startswith("skipped 'data/core'") or \
line.startswith("preprocessing specified resource:") or \
re.match("added .* defines.", line):
return ""
mo = re.match(r"\d+ /tmp(?:/wmlparser_.*?/|/)(.*\.cfg).*", line)
if mo:
@ -128,55 +124,55 @@ def main(folder):
return ""
mo = re.match(".*--preprocess-defines(.*)", line)
if mo: return "Defines: " + mo.group(1) + "<br />"
if mo:
return "Defines: " + mo.group(1) + '<br />'
for s in source:
line = line.replace(s, "WML")
line = line.replace("included from WML:1", "")
rows = line.replace("included from", "\n&nbsp;included from").splitlines()
out = ""
for row in rows:
row = row.strip()
out += row + "<br />"
out += row + '<br />'
return out
htmlerr = open(error_html, "w")
htmlerr.write("<html><body>")
htmlerr.write('<html><body>')
lines_count = 0
for line in text.splitlines():
line = line.strip()
if line in ["<INTERNAL ERROR>", "<WML ERROR>", "<PARSE ERROR>", "<TIMEOUT ERROR>"]:
htmlerr.write("<p>")
htmlerr.write('<p>')
elif line in ["</INTERNAL ERROR>", "</WML ERROR>", "</PARSE ERROR>", "</TIMEOUT ERROR>"]:
htmlerr.write("</p>")
htmlerr.write('</p>')
else:
err_html = postprocess(line)
lines_count += err_html.count("<br")
lines_count += err_html.count('<br')
htmlerr.write(err_html)
htmlerr.write("</body></html>")
total_lines += lines_count
total_error_logs += 1
w('<a class="error" href="%s">%s (%d lines)</a>' % (error_name, error_kind, lines_count))
w("</td></tr>")
count += 1
w("<tr><td>")
w("Total (for %d addons):" % count)
w("</td><td>")
w(str(total_n))
w("</td><td>")
w(str(total_error_logs) + " (" + str(total_lines) + " lines)")
w("</td></tr>")
htmlerr.write('</body></html>')
total_lines += lines_count
total_error_logs += 1
w('<a class="error" href="%s">%s (%d lines)</a>' % (html_output.cleanurl(error_name), error_kind, lines_count))
w('</td></tr>')
count += 1
w('</tbody>')
w('<tfoot><tr>')
w('<th scope="row">Total (for %d addons):</th>' % count)
w('<td>' + str(total_n) + '</td>')
w('<td>' + str(total_error_logs) + ' (' + str(total_lines) + ' lines)</td>')
w('</tr></tfoot>')
w('</table>')
w("</table>")
w('</div> <!-- overview -->')
w(html_output.html_footer % locals())
w(html_output.website_footer())
if __name__ == "__main__":
main(sys.argv[1])

View file

@ -1,352 +0,0 @@
/* Stylesheet for wmlunits HTML output. */
body {
background-color: #efebe0;
margin: 0px;
}
h1 {
text-align: center;
margin: 0 0 0.5em 0;
}
table.units {
background-color: #fffbf0;
margin: 0px;
border: none;
padding: 0px;
border-spacing: 0px;
width: auto;
}
div.header {
background:#444444 url("headerbg.jpg") repeat-x scroll center top;
border-bottom:1px solid #000000;
margin:0pt;
padding:0pt;
width:100%;
text-align: center;
}
div.topnav {
background:#272727 url("navbg.png") repeat-x scroll center bottom;
border-top:1px solid #595959;
margin:0;
padding:3px 4px 15px;
text-align:center;
}
div.topnav a {
color:#B48648;
font-family:"Trebuchet MS",sans-serif;
line-height:0.8em;
font-size:0.8em;
font-weight:bold;
text-decoration:none;
}
div.topnav a:hover {
color:#CCCCCC;
}
img {
border: none;
}
div.header img {
vertical-align:middle;
}
div.pic {
margin: 0px;
padding: 0px;
border: none;
display: inline;
float: left;
}
td.unitcell {
font-size: small;
text-align: center;
font-weight: bold;
border-bottom: 2px solid #cfcbc0;
border-top: 2px solid #fffbf0;
border-right: 2px solid #fffbf0;
}
td.unitcell a {
color: black;
font-size: medium;
text-decoration: none;
}
div.main {
margin-top: 0;
margin-left: 9em;
border-left: 1px solid black;
}
div.popupmenu
{
position: absolute;
margin-left: 8.5em;
margin-top: -1em;
background: white;
border: 1px solid black;
padding: 8px;
}
div.popupmenu > div
{
color: gray;
font-size: large;
border-bottom: 1px solid #000000;
margin-bottom: 0.5em;
}
div.popupmenu a {
text-decoration: none;
}
a.unitmenu {
display: block;
margin-left: 1em;
font-size: small;
}
.popuptrigger div.popupmenu
{
display: none;
}
.popuptrigger:hover div.popupmenu
{
display: block;
}
div.navbar {
width: 9em;
float: left;
overflow: hidden;
}
ul.navbar {
list-style: none;
margin: 0 0 0 0.5em;
border-spacing: 0px;
padding: 0px;
border-right: 1px solid black;
width: 8.5em;
}
ul.navbar li {
color: black;
font-weight: bold;
margin: 0px;
padding: 0px;
}
ul.navbar li a:hover {
color: #804000;
}
ul.navbar a {
color: #B48648;
font-size: large;
font-weight: bold;
cursor: pointer;
}
ul.navbar a.unitmenu {
font-size: medium;
}
ul.navbar div.popupmenu a {
font-size: medium;
}
td.raceheader {
background:#272727 url("navbg.png") repeat-x scroll center bottom;
border-top:1px solid #595959;
margin:0pt;
padding:1px 1px 10px 1px;
text-align:center;
color: #B48648;
font-size: x-large;
font-weight: bold;
}
tr.levels th {
border-bottom: 1px solid #cfcbc0;
}
table.unitinfo {
background-color: transparent;
border: none;
}
table.unitinfo th {
text-align: left;
font-weight: normal;
white-space: nowrap;
}
table.attacks col.col1 {
width: 7em;
}
table.resistances col.col1 {
width: 5em;
}
table.resistances col.col5 {
width: 5em;
}
table.resistances col.col3 {
width: 4em;
}
table.resistances img {
height: 36px;
}
table.terrain img {
height: 36px;
}
table.terrain th.numheader {
text-align: right;
}
td.grayed {
color: gray;
}
td.empty {
background-color: #fffbf0;
}
table.units col.col0 {
background-color: #efebe0;
width: 16%;
}
table.units col.col1 {
background-color: #f7ebd8;
width: 16%;
}
table.units col.col2 {
background-color: #f4ebdc;
width: 16%;
}
table.units col.col3 {
background-color: #efebe0;
width: 16%;
}
table.units col.col4 {
background-color: #f4ebdc;
width: 16%;
}
table.units col.col5 {
background-color: #f7ebd8;
width: 16%;
}
div.l {
color: gray;
font-weight: normal;
font-size: small;
border: 1px solid;
float: right;
margin: 0px;
}
div.i {
padding: 4px;
font-size: small;
border: 1px solid white;
float: left;
margin: 0px;
}
div.i a {
font-weight: 900;
color: #8080ff;
text-decoration: underline;
}
div.attributes {
font-size: small;
font-weight: normal;
text-align: left;
}
div#footer {
clear: both;
color: #111111;
font-size: small;
border-top: 1px solid #999999;;
padding: 1em;
margin: 1em;
}
div#footer p {
line-height: 1em;
padding: 0;
margin: 0;
}
div#footer a {
color: #111111;
}
td.num {
text-align: right;
}
td.val {
font-weight: bold;
}
div.unit-columns {
width: 51em;
float: left;
margin: 0 0 0 1em;
}
div.unit-columns i {
font-size: 140%;
font-style: normal;
}
div.unit-columns h1 {
text-shadow: 2px 2px 3px #998877;
text-align: left;
}
div.unit-columns h2 {
border-bottom: 1px solid #000000;
}
div.unit-columns h2 > small {
font-weight: normal;
font-size: medium;
}
div.unit-column-left {
width: 30em;
float: left;
}
div.unit-column-right {
margin: 0 0 0 1em;
width: 20em;
float: right;
}
table.overview {
border-spacing: 0px;
border: 1px solid #000000;
}
table.overview th {
text-align: center;
padding: 1em;
border-bottom: 2px solid #000000;
}
table.overview td {
border-bottom: 1px solid #000000;
}
table.overview td + td {
text-align: right;
}
table.overview a.error {
color: red;
}
div.overview {
margin: 0 auto;
background-color: #E7D9C0;
max-width: 60em;
width: auto;
border-radius: 1em 1em 1em 1em;
border: solid 1px white;
font-style: sans-serif;
padding: 1em;
}
div.portrait {
height: 200px;
}
div.portrait div {
position: absolute;
height: 200px;
width: 200px;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
opacity: 0.25;
}
div.portrait img {
position: absolute;
height: 200px;
}

View file

@ -7,12 +7,21 @@ Run without arguments to see usage.
"""
# Makes things faster on 32-bit systems
try: import psyco; psyco.full()
except ImportError: pass
import sys, os, glob, shutil, urllib.request, urllib.error, urllib.parse, argparse, traceback
import subprocess, yaml
import multiprocessing, queue
try:
import psyco
psyco.full()
except ImportError:
pass
import argparse
import os
import shutil
import sys
import traceback
import subprocess
import multiprocessing
import queue
import yaml
import wesnoth.wmlparser3 as wmlparser3
import unit_tree.helpers as helpers
@ -27,23 +36,15 @@ def copy_images():
print("Recolorizing pictures.")
image_collector.copy_and_color_images(options.output)
shutil.copy2(os.path.join(image_collector.datadir,
"data/tools/unit_tree/style.css"), options.output)
shutil.copy2(os.path.join(image_collector.datadir,
"data/tools/unit_tree/menu.js"), 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 = urllib.request.urlopen(grab)
open(local, "wb").write(url.read())
"data/tools/unit_tree/menu.js"),
options.output)
def shell(com):
#print(com)
p = subprocess.Popen(com, stdout = subprocess.PIPE,
stderr = subprocess.PIPE, shell = True)
p = subprocess.Popen(com,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=True)
out, err = p.communicate()
#if out: sys.stdout.write(out)
#if err: sys.stdout.write(err)
@ -52,7 +53,8 @@ def shell(com):
def shell_out(com):
p = subprocess.Popen(com,
stdout = subprocess.PIPE, stderr = subprocess.PIPE)
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
out, err = p.communicate()
return out
@ -67,7 +69,7 @@ def move(f, t, name):
com = "mv " + f + "/" + bash(name) + " " + t + "/"
return shell(com)
_info = {}
def get_info(addon):
global _info
@ -77,12 +79,13 @@ def get_info(addon):
try:
path = options.addons + "/" + addon + "/_info.cfg"
if os.path.exists(path):
parser = wmlparser3.Parser(options.wesnoth, options.config_dir,
options.data_dir)
parser = wmlparser3.Parser(options.wesnoth,
options.config_dir,
options.data_dir)
parser.parse_file(path)
_info[addon] = parser
else:
print(("Cannot find " + path))
print("Cannot find " + path)
except wmlparser3.WMLError as e:
print(e)
return _info[addon]
@ -96,7 +99,7 @@ def get_dependencies(addon):
return _deps[addon]
_deps[addon] = []
try:
info = get_info(addon).get_all(tag = "info")[0]
info = get_info(addon).get_all(tag="info")[0]
row = info.get_text_val("dependencies")
if row:
deps1 = row.split(",")
@ -106,7 +109,7 @@ def get_dependencies(addon):
if d in global_addons:
_deps[addon].append(d)
else:
print(("Missing dependency for " + addon + ": " + d))
print("Missing dependency for " + addon + ": " + d)
except Exception as e:
print(e)
return _deps[addon]
@ -119,8 +122,8 @@ def get_all_dependencies(addon):
check = get_dependencies(addon)[:]
while check:
d = check.pop()
if d == addon: continue
if d in result: continue
if d == addon or d in result:
continue
result.append(d)
check += get_dependencies(d)
return result
@ -140,33 +143,32 @@ def sorted_by_dependencies(addons):
n += 1
continue
if n == 0:
print(("Cannot sort dependencies for these addons: " + str(unsorted)))
sorted += unsorted
break
print("Cannot sort dependencies for these addons: " + str(unsorted))
sorted += unsorted
break
return sorted
def search(batchlist, name):
for info in batchlist:
if info and info["name"] == name: return info
if info and info["name"] == name:
return info
batchlist.append({})
batchlist[-1]["name"] = name
return batchlist[-1]
def list_contents():
class Empty: pass
local = Empty()
class Empty:
pass
local = Empty()
mainline_eras = set()
filename = options.list
def append(info, id, define, c = None, name = None, domain = None):
def append(info, id, define, c=None, name=None, domain=None):
info.append({})
info[-1]["id"]= id
info[-1]["id"] = id
info[-1]["define"] = define
if c:
info[-1]["name"] = c.get_text_val("name")
else:
info[-1]["name"] = name
info[-1]["name"] = c.get_text_val("name") if c else name
info[-1]["units"] = "?"
info[-1]["translations"] = {}
@ -175,7 +177,7 @@ def list_contents():
def translate(string, domain):
return translation.translate(string, domain)
if c:
t = c.get_text_val("name", translation = translate)
t = c.get_text_val("name", translation=translate)
else:
t = translate(name, domain)
if t != info[-1]["name"]:
@ -190,7 +192,7 @@ def list_contents():
return dependency_eras
def list_eras(batchlist, addon):
eras = local.wesnoth.parser.get_all(tag = "era")
eras = local.wesnoth.parser.get_all(tag="era")
if addon != "mainline":
dependency_eras = get_dependency_eras(batchlist, addon)
eras = [x for x in eras if not x.get_text_val("id") in dependency_eras]
@ -199,30 +201,31 @@ def list_contents():
eid = era.get_text_val("id")
if addon == "mainline":
mainline_eras.add(eid)
append(info, eid, "MULTIPLAYER", c = era)
append(info, eid, "MULTIPLAYER", c=era)
return info
def list_campaigns(batchlist, addon):
campaigns = local.wesnoth.parser.get_all(tag = "campaign")
campaigns = local.wesnoth.parser.get_all(tag="campaign")
info = []
for campaign in campaigns:
cid = campaign.get_text_val("id")
d = campaign.get_text_val("define")
d2 = campaign.get_text_val("extra_defines")
if d2: d += "," + d2
if d2:
d += "," + d2
d3 = campaign.get_text_val("difficulties")
if d3:
d += "," + d3.split(",")[0]
else:
difficulty = campaign.get_all(tag = "difficulty")
difficulty = campaign.get_all(tag="difficulty")
if difficulty:
d3 = difficulty[0].get_text_val("define")
if d3:
d += "," + d3
append(info, cid, d, c = campaign)
append(info, cid, d, c=campaign)
return info
@ -241,10 +244,10 @@ def list_contents():
q.put(("e", e))
q = multiprocessing.Queue()
p = multiprocessing.Process(target = f, args = (options, wml, defines, q))
p = multiprocessing.Process(target=f, args=(options, wml, defines, q))
p.start()
try:
s, local.wesnoth = q.get(timeout = TIMEOUT)
s, local.wesnoth = q.get(timeout=TIMEOUT)
except queue.Empty:
p.terminate()
raise
@ -253,15 +256,17 @@ def list_contents():
if s == "e":
remote_exception = local.wesnoth
raise remote_exception
def get_version(addon):
parser = get_info(addon)
if parser:
for info in parser.get_all(tag = "info"):
for info in parser.get_all(tag="info"):
return info.get_text_val("version") + "*" + info.get_text_val("uploads")
try: os.makedirs(options.output + "/mainline")
except OSError: pass
try:
os.makedirs(options.output + "/mainline")
except OSError:
pass
try:
batchlist = yaml.load(open(options.list))
@ -279,8 +284,8 @@ def list_contents():
# Fake mainline campaign to have an overview of the mainline units
info["campaigns"] = []
append(info["campaigns"], "mainline", "", name = "Units", domain = "wesnoth-help")
append(info["campaigns"], "mainline", "", name="Units", domain="wesnoth-help")
if not options.addons_only:
parse("{core}{campaigns}", "SKIP_CORE")
info["campaigns"] += list_campaigns(batchlist, "mainline")
@ -300,7 +305,8 @@ def list_contents():
addons = sorted_by_dependencies(addons)
for i, addon in enumerate(addons):
if not os.path.isdir(options.addons + "/" + addon): continue
if not os.path.isdir(options.addons + "/" + addon):
continue
mem = "? KB"
try:
mem = dict([x.split("\t", 1) for x in open("/proc/self/status").read().split("\n") if x])["VmRSS:"]
@ -310,16 +316,17 @@ def list_contents():
sys.stdout.flush()
d = options.output + "/" + addon
logname = d + "/error.log"
try: os.makedirs(d)
except OSError: pass
try:
os.makedirs(d)
except OSError:
pass
version = get_version(addon)
move(options.addons, options.config_dir + "/data/add-ons", addon)
for d in get_dependencies(addon):
move(options.addons, options.config_dir + "/data/add-ons", d)
try:
info = search(batchlist, addon)
if info.get("version", "") == version and info.get("parsed", False) == True:
if info.get("version", "") == version and info.get("parsed", False) is True:
sys.stdout.write("up to date\n")
continue
info["parsed"] = False
@ -350,18 +357,17 @@ def list_contents():
ef.write("</INTERNAL ERROR>\n")
ef.close()
sys.stdout.write("failed\n")
finally:
move(options.config_dir + "/data/add-ons", options.addons, addon)
for d in get_dependencies(addon):
move(options.config_dir + "/data/add-ons", options.addons, d)
yaml.safe_dump(batchlist, open(filename, "w"),
encoding = "utf-8", default_flow_style = False)
encoding="utf-8", default_flow_style=False)
def process_campaign_or_era(addon, cid, define, batchlist):
n = 0
print(str(addon) + ": " + str(cid) + " " + str(define))
if not define:
@ -374,10 +380,10 @@ def process_campaign_or_era(addon, cid, define, batchlist):
options.transdir)
wesnoth.batchlist = batchlist
wesnoth.cid = cid
wesnoth.parser.parse_text("{core/units.cfg}", "NORMAL")
wesnoth.add_units("mainline")
if define == "MULTIPLAYER":
wesnoth.parser.parse_text("{core}{multiplayer}{multiplayer/eras.cfg}{~add-ons}", "MULTIPLAYER,SKIP_CORE")
wesnoth.add_units(cid)
@ -389,19 +395,19 @@ def process_campaign_or_era(addon, cid, define, batchlist):
else:
wesnoth.parser.parse_text("{core}{~add-ons}", "SKIP_CORE," + define)
wesnoth.add_units(cid)
if addon == "mainline" and cid == "mainline":
write_animation_statistics(wesnoth)
wesnoth.add_binary_paths(addon, image_collector)
if define == "MULTIPLAYER":
eras = wesnoth.parser.get_all(tag = "era")
eras = wesnoth.parser.get_all(tag="era")
for era in eras:
wesnoth.add_era(era)
wesnoth.find_unit_factions()
else:
campaigns = wesnoth.parser.get_all(tag = "campaign")
campaigns = wesnoth.parser.get_all(tag="campaign")
for campaign in campaigns:
wesnoth.add_campaign(campaign)
@ -410,9 +416,8 @@ def process_campaign_or_era(addon, cid, define, batchlist):
wesnoth.check_units()
for isocode in languages:
if addon != "mainline" and isocode != "en_US": continue
if addon != "mainline" and isocode != "en_US":
continue
if define == "MULTIPLAYER":
for era in list(wesnoth.era_lookup.values()):
if era.get_text_val("id") == cid:
@ -426,7 +431,6 @@ def process_campaign_or_era(addon, cid, define, batchlist):
if campaign.get_text_val("id") == cid:
n = html_output.generate_campaign_report(addon, isocode, campaign, wesnoth)
break
html_output.generate_single_unit_reports(addon, isocode, wesnoth)
return n
@ -437,12 +441,13 @@ def batch_process():
for addon in batchlist:
name = addon["name"]
set_dependencies(name, addon.get("dependencies", []))
for addon in batchlist:
name = addon["name"]
if not options.reparse and addon.get("parsed", False) == True: continue
if not options.reparse and addon.get("parsed", False) == True:
continue
if name == "mainline":
worked = True
else:
@ -450,22 +455,24 @@ def batch_process():
for d in addon.get("dependencies", []):
move(options.addons, options.config_dir + "/data/add-ons", d)
d = options.output + "/" + name
try: os.makedirs(d)
except OSError: pass
try:
os.makedirs(d)
except OSError:
pass
logname = d + "/error.log"
def err(mess):
ef = open(logname, "a")
ef.write(str(mess))
ef.close()
html_output.write_error = err
try:
if not worked:
print((name + " not found"))
print(name + " not found")
continue
for era in addon.get("eras", []):
eid = era["id"]
n = process_campaign_or_era(name, eid, era["define"], batchlist)
@ -473,21 +480,23 @@ def batch_process():
for campaign in addon.get("campaigns", []):
cid = campaign["id"]
if cid == None: cid = campaign["define"]
if cid == None: cid = name
if cid is None:
cid = campaign["define"]
if cid is None:
cid = name
n = process_campaign_or_era(name, cid, campaign["define"], batchlist)
campaign["units"] = n
except wmlparser3.WMLError as e:
ef = open(logname, "a")
ef.write("<WML ERROR>\n")
ef.write(str(e))
ef.write("</WML ERROR>\n")
ef.close()
print((" " + name + " failed"))
print(" " + name + " failed")
except Exception as e:
traceback.print_exc()
print((" " + name + " failed"))
print(" " + name + " failed")
ef = open(logname, "a")
ef.write("<INTERNAL ERROR>\n")
ef.write("please report as bug\n")
@ -499,15 +508,15 @@ def batch_process():
move(options.config_dir + "/data/add-ons", options.addons, name)
for d in addon.get("dependencies", []):
move(options.config_dir + "/data/add-ons", options.addons, d)
addon["parsed"] = True
yaml.safe_dump(batchlist, open(options.batch, "w"),
encoding = "utf-8", default_flow_style = False)
encoding="utf-8", default_flow_style=False)
try:
unit_tree.overview.write_addon_overview(os.path.join(options.output,
name), addon)
unit_tree.overview.write_addon_overview(
os.path.join(options.output, name), addon)
except Exception as e:
pass
@ -518,8 +527,9 @@ def write_unit_ids_UNUSED():
uids = list(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)
wesnoth.unit_lookup[u2].rid)
if r == 0:
r = cmp(u1, u2)
return r
uids.sort(by_race)
race = None
@ -528,7 +538,8 @@ def write_unit_ids_UNUSED():
for uid in uids:
u = wesnoth.unit_lookup[uid]
if u.rid != race:
if race != None: f.write("</ul>")
if race is not None:
f.write("</ul>")
f.write("<p>%s</p>\n" % (u.rid,))
f.write("<ul>")
race = u.rid
@ -544,7 +555,6 @@ def write_animation_statistics(wesnoth):
f.close()
if __name__ == '__main__':
# We change the process name to "wmlunits"
try:
import ctypes
@ -558,41 +568,41 @@ if __name__ == '__main__':
ap = argparse.ArgumentParser()
ap.add_argument("-C", "--config-dir",
help="Specify the user configuration dir (wesnoth --config-path).")
help="Specify the user configuration dir (wesnoth --config-path).")
ap.add_argument("-D", "--data-dir",
help="Specify the wesnoth data dir (wesnoth --path).")
help="Specify the wesnoth data dir (wesnoth --path).")
ap.add_argument("-l", "--language", default="all",
help="Specify a language to use. Else output is produced for all languages.")
help="Specify a language to use. Else output is produced for all languages.")
ap.add_argument("-o", "--output",
help="Specify the output directory.")
help="Specify the output directory.")
ap.add_argument("-n", "--nocopy", action="store_true",
help="No copying of files. By default all images are copied to the output dir.")
help="No copying of files. By default all images are copied to the output dir.")
ap.add_argument("-w", "--wesnoth",
help="Specify the wesnoth executable to use. Whatever data " +
"and config paths that executable is configured for will be " +
"used to find game files and addons.")
help="Specify the wesnoth executable to use. Whatever data " +
"and config paths that executable is configured for will be " +
"used to find game files and addons.")
ap.add_argument("-t", "--transdir",
help="Specify the directory with gettext message catalogues. " +
"Defaults to ./translations.", default="translations")
help="Specify the directory with gettext message catalogues. " +
"Defaults to ./translations.", default="translations")
ap.add_argument("-T", "--timeout",
help="Use the timeout iven in seconds when parsing WML, default is 20 " +
"seconds.")
help="Use the timeout iven in seconds when parsing WML, default is 20 " +
"seconds.")
ap.add_argument("-r", "--reparse", action="store_true",
help="Reparse everything.")
help="Reparse everything.")
ap.add_argument("-a", "--addons",
help="Specify path to a folder with all addons. This should be " +
"outside the user config folder.")
help="Specify path to a folder with all addons. This should be " +
"outside the user config folder.")
ap.add_argument("-L", "--list",
help = "List available eras and campaigns.")
help="List available eras and campaigns.")
ap.add_argument("-B", "--batch",
help = "Batch process the given list.")
ap.add_argument("-A", "--addons-only", action = "store_true",
help = "Do only process addons (for debugging).")
ap.add_argument("-v", "--verbose", action = "store_true")
ap.add_argument("-W", "--wiki", action = "store_true",
help = "write wikified units list to stdout")
help="Batch process the given list.")
ap.add_argument("-A", "--addons-only", action="store_true",
help="Do only process addons (for debugging).")
ap.add_argument("-v", "--verbose", action="store_true")
ap.add_argument("-W", "--wiki", action="store_true",
help="write wikified units list to stdout")
options = ap.parse_args()
html_output.options = options
helpers.options = options
unit_tree.overview.options = options
@ -614,27 +624,29 @@ if __name__ == '__main__':
if not options.data_dir:
options.data_dir = shell_out([options.wesnoth, "--path"]).strip().decode("utf8")
print(("Using " + options.data_dir + " as data dir."))
print("Using " + options.data_dir + " as data dir.")
if not options.config_dir:
options.config_dir = shell_out([options.wesnoth, "--config-path"]).strip().decode("utf8")
print(("Using " + options.config_dir + " as config dir."))
print("Using " + options.config_dir + " as config dir.")
if not options.transdir:
options.transdir = os.getcwd()
if options.wiki:
wiki_output.main()
sys.exit(0)
image_collector = helpers.ImageCollector(options.wesnoth,
options.config_dir, options.data_dir)
options.config_dir,
options.data_dir)
html_output.image_collector = image_collector
if options.language == "all":
languages = []
parser = wmlparser3.Parser(options.wesnoth, options.config_dir,
options.data_dir)
parser = wmlparser3.Parser(options.wesnoth,
options.config_dir,
options.data_dir)
parser.parse_text("{languages}")
for locale in parser.get_all(tag="locale"):
@ -650,22 +662,21 @@ if __name__ == '__main__':
if not options.list and not options.batch:
sys.stderr.write("Need --list or --batch (or both).\n")
sys.exit(-1)
if options.output:
# Generate output dir.
if not os.path.isdir(options.output):
os.mkdir(options.output)
if options.list:
list_contents()
if options.batch:
batch_process()
unit_tree.overview.main(options.output)
if not options.nocopy:
copy_images()
html_output.write_index(options.output)
html_output.write_index(options.output)