diff --git a/data/tools/wesnoth/wmltools.py b/data/tools/wesnoth/wmltools.py index 303b4df85bd3..1fd79ba3943b 100644 --- a/data/tools/wesnoth/wmltools.py +++ b/data/tools/wesnoth/wmltools.py @@ -3,7 +3,9 @@ """ -import collections +from __future__ import unicode_literals + +import collections, codecs import sys, os, re, sre_constants, hashlib, glob, gzip import string @@ -188,9 +190,11 @@ def issave(filename): if isresource(filename): return False if filename.endswith(".gz"): - firstline = gzip.open(filename).readline() + with gzip.open(filename) as content: + firstline = content.readline() else: - firstline = open(filename).readline() + with codecs.open(filename, "r", "utf8") as content: + firstline = content.readline() return firstline.startswith("label=") def isresource(filename): @@ -462,132 +466,131 @@ def visible_from(self, defn, fn, n): def scan_for_definitions(self, namespace, filename): ignoreflag = False conditionalsflag = False - dfp = open(filename) - state = "outside" - latch_unit = in_base_unit = in_theme = False - for (n, line) in enumerate(dfp): - if self.warnlevel > 1: - print repr(line)[1:-1] - if line.strip().startswith("#textdomain"): - continue - m = re.search("# *wmlscope: warnlevel ([0-9]*)", line) - if m: - self.warnlevel = int(m.group(1)) - print '"%s", line %d: warnlevel set to %d (definition-gathering pass)' \ - % (filename, n+1, self.warnlevel) - continue - m = re.search("# *wmlscope: set *([^=]*)=(.*)", line) - if m: - prop = m.group(1).strip() - value = m.group(2).strip() - if namespace not in self.properties: - self.properties[namespace] = {} - self.properties[namespace][prop] = value - m = re.search("# *wmlscope: prune (.*)", line) - if m: - name = m.group(1) - if self.warnlevel >= 2: - print '"%s", line %d: pruning definitions of %s' \ - % (filename, n+1, name ) - if name not in self.xref: - print >>sys.stderr, "wmlscope: can't prune undefined macro %s" % name - else: - self.xref[name] = self.xref[name][:1] - continue - if "# wmlscope: start conditionals" in line: - if self.warnlevel > 1: - print '"%s", line %d: starting conditionals' \ - % (filename, n+1) - conditionalsflag = True - elif "# wmlscope: stop conditionals" in line: + with codecs.open(filename, "r", "utf8") as dfp: + state = "outside" + latch_unit = in_base_unit = in_theme = False + for (n, line) in enumerate(dfp): if self.warnlevel > 1: - print '"%s", line %d: stopping conditionals' \ - % (filename, n+1) - conditionalsflag = False - if "# wmlscope: start ignoring" in line: - if self.warnlevel > 1: - print '"%s", line %d: starting ignoring (definition pass)' \ - % (filename, n+1) - ignoreflag = True - elif "# wmlscope: stop ignoring" in line: - if self.warnlevel > 1: - print '"%s", line %d: stopping ignoring (definition pass)' \ - % (filename, n+1) - ignoreflag = False - elif ignoreflag: - continue - if line.strip().startswith("#define"): - tokens = line.split() - if len(tokens) < 2: - print >>sys.stderr, \ - '"%s", line %d: malformed #define' \ - % (filename, n+1) - else: + print repr(line)[1:-1] + if line.strip().startswith("#textdomain"): + continue + m = re.search("# *wmlscope: warnlevel ([0-9]*)", line) + if m: + self.warnlevel = int(m.group(1)) + print '"%s", line %d: warnlevel set to %d (definition-gathering pass)' \ + % (filename, n+1, self.warnlevel) + continue + m = re.search("# *wmlscope: set *([^=]*)=(.*)", line) + if m: + prop = m.group(1).strip() + value = m.group(2).strip() + if namespace not in self.properties: + self.properties[namespace] = {} + self.properties[namespace][prop] = value + m = re.search("# *wmlscope: prune (.*)", line) + if m: + name = m.group(1) + if self.warnlevel >= 2: + print '"%s", line %d: pruning definitions of %s' \ + % (filename, n+1, name ) + if name not in self.xref: + print >>sys.stderr, "wmlscope: can't prune undefined macro %s" % name + else: + self.xref[name] = self.xref[name][:1] + continue + if "# wmlscope: start conditionals" in line: + if self.warnlevel > 1: + print '"%s", line %d: starting conditionals' \ + % (filename, n+1) + conditionalsflag = True + elif "# wmlscope: stop conditionals" in line: + if self.warnlevel > 1: + print '"%s", line %d: stopping conditionals' \ + % (filename, n+1) + conditionalsflag = False + if "# wmlscope: start ignoring" in line: + if self.warnlevel > 1: + print '"%s", line %d: starting ignoring (definition pass)' \ + % (filename, n+1) + ignoreflag = True + elif "# wmlscope: stop ignoring" in line: + if self.warnlevel > 1: + print '"%s", line %d: stopping ignoring (definition pass)' \ + % (filename, n+1) + ignoreflag = False + elif ignoreflag: + continue + if line.strip().startswith("#define"): + tokens = line.split() + if len(tokens) < 2: + print >>sys.stderr, \ + '"%s", line %d: malformed #define' \ + % (filename, n+1) + else: + name = tokens[1] + here = Reference(namespace, filename, n+1, line, args=tokens[2:]) + here.hash = hashlib.md5() + here.docstring = line.lstrip()[8:] # Strip off #define_ + state = "macro_header" + continue + elif state != 'outside' and line.strip().endswith("#enddef"): + here.hash.update(line) + here.hash = here.hash.digest() + if name in self.xref: + for defn in self.xref[name]: + if not self.visible_from(defn, filename, n+1): + continue + elif conditionalsflag: + continue + elif defn.hash != here.hash: + print >>sys.stderr, \ + "%s: overrides different %s definition at %s" \ + % (here, name, defn) + elif self.warnlevel > 0: + print >>sys.stderr, \ + "%s: duplicates %s definition at %s" \ + % (here, name, defn) + if name not in self.xref: + self.xref[name] = [] + self.xref[name].append(here) + state = "outside" + elif state == "macro_header" and line.strip() and line.strip()[0] != "#": + state = "macro_body" + if state == "macro_header": + # Ignore macro header commends that are pragmas + if "wmlscope" not in line and "wmllint:" not in line: + here.docstring += line.lstrip()[1:] + if state in ("macro_header", "macro_body"): + here.hash.update(line) + elif line.strip().startswith("#undef"): + tokens = line.split() name = tokens[1] - here = Reference(namespace, filename, n+1, line, args=tokens[2:]) - here.hash = hashlib.md5() - here.docstring = line.lstrip()[8:] # Strip off #define_ - state = "macro_header" - continue - elif state != 'outside' and line.strip().endswith("#enddef"): - here.hash.update(line) - here.hash = here.hash.digest() - if name in self.xref: - for defn in self.xref[name]: - if not self.visible_from(defn, filename, n+1): - continue - elif conditionalsflag: - continue - elif defn.hash != here.hash: - print >>sys.stderr, \ - "%s: overrides different %s definition at %s" \ - % (here, name, defn) - elif self.warnlevel > 0: - print >>sys.stderr, \ - "%s: duplicates %s definition at %s" \ - % (here, name, defn) - if name not in self.xref: - self.xref[name] = [] - self.xref[name].append(here) - state = "outside" - elif state == "macro_header" and line.strip() and line.strip()[0] != "#": - state = "macro_body" - if state == "macro_header": - # Ignore macro header commends that are pragmas - if "wmlscope" not in line and "wmllint:" not in line: - here.docstring += line.lstrip()[1:] - if state in ("macro_header", "macro_body"): - here.hash.update(line) - elif line.strip().startswith("#undef"): - tokens = line.split() - name = tokens[1] - if name in self.xref and self.xref[name]: - self.xref[name][-1].undef = n+1 - else: - print "%s: unbalanced #undef on %s" \ - % (Reference(namespace, filename, n+1), name) - if state == 'outside': - if '[unit_type]' in line: - latch_unit = True - elif '[/unit_type]' in line: - latch_unit = False - elif '[base_unit]' in line: - in_base_unit = True - elif '[/base_unit]' in line: - in_base_unit = False - elif '[theme]' in line: - in_theme = True - elif '[/theme]' in line: - in_theme = False - elif latch_unit and not in_base_unit and not in_theme and "id" in line: - m = CrossRef.tag_parse.search(line) - if m and m.group(1) == "id": - uid = m.group(2) - if uid not in self.unit_ids: - self.unit_ids[uid] = [] - self.unit_ids[uid].append(Reference(namespace, filename, n+1)) - latch_unit= False - dfp.close() + if name in self.xref and self.xref[name]: + self.xref[name][-1].undef = n+1 + else: + print "%s: unbalanced #undef on %s" \ + % (Reference(namespace, filename, n+1), name) + if state == 'outside': + if '[unit_type]' in line: + latch_unit = True + elif '[/unit_type]' in line: + latch_unit = False + elif '[base_unit]' in line: + in_base_unit = True + elif '[/base_unit]' in line: + in_base_unit = False + elif '[theme]' in line: + in_theme = True + elif '[/theme]' in line: + in_theme = False + elif latch_unit and not in_base_unit and not in_theme and "id" in line: + m = CrossRef.tag_parse.search(line) + if m and m.group(1) == "id": + uid = m.group(2) + if uid not in self.unit_ids: + self.unit_ids[uid] = [] + self.unit_ids[uid].append(Reference(namespace, filename, n+1)) + latch_unit= False def __init__(self, dirpath=[], exclude="", warnlevel=0, progress=False): "Build cross-reference object from the specified filelist." self.filelist = Forest(dirpath, exclude) @@ -615,10 +618,9 @@ def __init__(self, dirpath=[], exclude="", warnlevel=0, progress=False): elif filename.endswith(".def"): # It's a list of names to be considered defined self.noxref = True - dfp = open(filename) - for line in dfp: - self.xref[line.strip()] = True - dfp.close() + with codecs.open(filename, "r", "utf8") as dfp: + for line in dfp: + self.xref[line.strip()] = True # Next, decorate definitions with all references from the filelist. self.unresolved = [] self.missing = [] @@ -630,133 +632,132 @@ def __init__(self, dirpath=[], exclude="", warnlevel=0, progress=False): if progress: print filename if iswml(fn): - rfp = open(fn) - attack_name = None - beneath = 0 - ignoreflag = False - for (n, line) in enumerate(rfp): - if line.strip().startswith("#define"): - formals = line.strip().split()[2:] - elif line.startswith("#enddef"): - formals = [] - comment = "" - if '#' in line: - if "# wmlscope: start ignoring" in line: - if self.warnlevel > 1: - print '"%s", line %d: starting ignoring (reference pass)' \ - % (fn, n+1) - ignoreflag = True - elif "# wmlscope: stop ignoring" in line: - if self.warnlevel > 1: - print '"%s", line %d: stopping ignoring (reference pass)' \ - % (fn, n+1) - ignoreflag = False - m = re.search("# *wmlscope: self.warnlevel ([0-9]*)", line) - if m: - self.warnlevel = int(m.group(1)) - print '"%s", line %d: self.warnlevel set to %d (reference-gathering pass)' \ - % (fn, n+1, self.warnlevel) + with codecs.open(fn, "r", "utf8") as rfp: + attack_name = None + beneath = 0 + ignoreflag = False + for (n, line) in enumerate(rfp): + if line.strip().startswith("#define"): + formals = line.strip().split()[2:] + elif line.startswith("#enddef"): + formals = [] + comment = "" + if '#' in line: + if "# wmlscope: start ignoring" in line: + if self.warnlevel > 1: + print '"%s", line %d: starting ignoring (reference pass)' \ + % (fn, n+1) + ignoreflag = True + elif "# wmlscope: stop ignoring" in line: + if self.warnlevel > 1: + print '"%s", line %d: stopping ignoring (reference pass)' \ + % (fn, n+1) + ignoreflag = False + m = re.search("# *wmlscope: self.warnlevel ([0-9]*)", line) + if m: + self.warnlevel = int(m.group(1)) + print '"%s", line %d: self.warnlevel set to %d (reference-gathering pass)' \ + % (fn, n+1, self.warnlevel) + continue + fields = line.split('#') + line = fields[0] + if len(fields) > 1: + comment = fields[1] + if ignoreflag or not line: continue - fields = line.split('#') - line = fields[0] - if len(fields) > 1: - comment = fields[1] - if ignoreflag or not line: - continue - # Find references to macros - for match in re.finditer(CrossRef.macro_reference, line): - name = match.group(1) - candidates = [] - if self.warnlevel >=2: - print '"%s", line %d: seeking definition of %s' \ - % (fn, n+1, name) - if name in formals: + # Find references to macros + for match in re.finditer(CrossRef.macro_reference, line): + name = match.group(1) + candidates = [] + if self.warnlevel >=2: + print '"%s", line %d: seeking definition of %s' \ + % (fn, n+1, name) + if name in formals: + continue + elif name in self.xref: + # Count the number of actual arguments. + # Set args to None if the call doesn't + # close on this line + (args, brackdepth, parendepth) = parse_macroref(match.start(0), line) + if brackdepth > 0 or parendepth > 0: + args = None + else: + args.pop(0) + #if args: + # print '"%s", line %d: args of %s is %s' \ + # % (fn, n+1, name, args) + # Figure out which macros might resolve this + for defn in self.xref[name]: + if self.visible_from(defn, fn, n+1): + defn.append(fn, n+1, args) + candidates.append(str(defn)) + if len(candidates) > 1: + print "%s: more than one definition of %s is visible here (%s)." % (Reference(ns, fn, n), name, "; ".join(candidates)) + if len(candidates) == 0: + self.unresolved.append((name,Reference(ns,fn,n+1))) + # Don't be fooled by HTML image references in help strings. + if "" in line: continue - elif name in self.xref: - # Count the number of actual arguments. - # Set args to None if the call doesn't - # close on this line - (args, brackdepth, parendepth) = parse_macroref(match.start(0), line) - if brackdepth > 0 or parendepth > 0: - args = None + # Find references to resource files + for match in re.finditer(CrossRef.file_reference, line): + name = match.group(0) + # Catches maps that look like macro names. + if (name.endswith(".map") or name.endswith(".mask")) and name[0] == '{': + name = name[1:] + if os.sep == "\\": + name = name.replace("/", "\\") + key = None + # If name is already in our resource list, it's easy. + if name in self.fileref and self.visible_from(name, fn, n): + self.fileref[name].append(fn, n+1) + continue + # If the name contains substitutable parts, count + # it as a reference to everything the substitutions + # could potentially match. + elif '{' in name or '@' in name: + pattern = re.sub(r"(\{[^}]*\}|@R0|@V)", '.*', name) + key = self.mark_matching_resources(pattern, fn,n+1) + if key: + self.fileref[key].append(fn, n+1) else: - args.pop(0) - #if args: - # print '"%s", line %d: args of %s is %s' \ - # % (fn, n+1, name, args) - # Figure out which macros might resolve this - for defn in self.xref[name]: - if self.visible_from(defn, fn, n+1): - defn.append(fn, n+1, args) - candidates.append(str(defn)) - if len(candidates) > 1: - print "%s: more than one definition of %s is visible here (%s)." % (Reference(ns, fn, n), name, "; ".join(candidates)) - if len(candidates) == 0: - self.unresolved.append((name,Reference(ns,fn,n+1))) - # Don't be fooled by HTML image references in help strings. - if "" in line: - continue - # Find references to resource files - for match in re.finditer(CrossRef.file_reference, line): - name = match.group(0) - # Catches maps that look like macro names. - if (name.endswith(".map") or name.endswith(".mask")) and name[0] == '{': - name = name[1:] - if os.sep == "\\": - name = name.replace("/", "\\") - key = None - # If name is already in our resource list, it's easy. - if name in self.fileref and self.visible_from(name, fn, n): - self.fileref[name].append(fn, n+1) - continue - # If the name contains substitutable parts, count - # it as a reference to everything the substitutions - # could potentially match. - elif '{' in name or '@' in name: - pattern = re.sub(r"(\{[^}]*\}|@R0|@V)", '.*', name) - key = self.mark_matching_resources(pattern, fn,n+1) - if key: - self.fileref[key].append(fn, n+1) - else: - candidates = [] - for trial in self.fileref: - if trial.endswith(os.sep + name) and self.visible_from(trial, fn, n): - key = trial - self.fileref[trial].append(fn, n+1) - candidates.append(trial) - if len(candidates) > 1: - print "%s: more than one resource matching %s is visible here (%s)." % (Reference(ns,fn, n), name, ", ".join(candidates)) - if not key: - self.missing.append((name, Reference(ns,fn,n+1))) - # Notice implicit references through attacks - if state == "outside": - if "[attack]" in line: - beneath = 0 - attack_name = default_icon = None - have_icon = False - elif "name=" in line and not "no-icon" in comment: - attack_name = line[line.find("name=")+5:].strip() - default_icon = os.path.join("attacks", attack_name + ".png") - elif "icon=" in line and beneath == 0: - have_icon = True - elif "[/attack]" in line: - if attack_name and not have_icon: candidates = [] - key = None for trial in self.fileref: - if trial.endswith(os.sep + default_icon) and self.visible_from(trial, fn, n): + if trial.endswith(os.sep + name) and self.visible_from(trial, fn, n): key = trial self.fileref[trial].append(fn, n+1) candidates.append(trial) if len(candidates) > 1: - print "%s: more than one definition of %s is visible here (%s)." % (Reference(ns,fn, n), name, ", ".join(candidates)) + print "%s: more than one resource matching %s is visible here (%s)." % (Reference(ns,fn, n), name, ", ".join(candidates)) if not key: - self.missing.append((default_icon, Reference(ns,fn,n+1))) - elif line.strip().startswith("[/"): - beneath -= 1 - elif line.strip().startswith("["): - beneath += 1 - rfp.close() + self.missing.append((name, Reference(ns,fn,n+1))) + # Notice implicit references through attacks + if state == "outside": + if "[attack]" in line: + beneath = 0 + attack_name = default_icon = None + have_icon = False + elif "name=" in line and not "no-icon" in comment: + attack_name = line[line.find("name=")+5:].strip() + default_icon = os.path.join("attacks", attack_name + ".png") + elif "icon=" in line and beneath == 0: + have_icon = True + elif "[/attack]" in line: + if attack_name and not have_icon: + candidates = [] + key = None + for trial in self.fileref: + if trial.endswith(os.sep + default_icon) and self.visible_from(trial, fn, n): + key = trial + self.fileref[trial].append(fn, n+1) + candidates.append(trial) + if len(candidates) > 1: + print "%s: more than one definition of %s is visible here (%s)." % (Reference(ns,fn, n), name, ", ".join(candidates)) + if not key: + self.missing.append((default_icon, Reference(ns,fn,n+1))) + elif line.strip().startswith("[/"): + beneath -= 1 + elif line.strip().startswith("["): + beneath += 1 # Check whether each namespace has a defined export property for namespace in self.dirpath: if namespace not in self.properties or "export" not in self.properties[namespace]: