mirror of
https://git.code.sf.net/p/quake/quakeforge
synced 2024-11-26 14:20:59 +00:00
6e8a3b8153
Id's comments are a little inconsistent, but for the most part usable info can be extracted. While not yet supported, Arcane Dimensions' comments are extremely consistent (just some issues with hyphen counts in separators), so parsing out usable info will be fairly easy. The hard part will be presenting it.
260 lines
9.6 KiB
Python
260 lines
9.6 KiB
Python
# vim:ts=4:et
|
|
# ##### BEGIN GPL LICENSE BLOCK #####
|
|
#
|
|
# This program is free software; you can redistribute it and/or
|
|
# modify it under the terms of the GNU General Public License
|
|
# as published by the Free Software Foundation; either version 2
|
|
# of the License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software Foundation,
|
|
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
#
|
|
# ##### END GPL LICENSE BLOCK #####
|
|
|
|
# <pep8 compliant>
|
|
|
|
quotables = ("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
+ "`abcdefghijklmnopqrstuvwxyz!#$%&*+-./:?@|~_^")
|
|
|
|
class PListError(Exception):
|
|
def __init__(self, line, message):
|
|
Exception.__init__(self, "%d: %s" % (line, message))
|
|
self.line = line
|
|
|
|
class pldata:
|
|
def __init__(self, src = ''):
|
|
self.src = src
|
|
self.pos = 0;
|
|
self.end = len(self.src)
|
|
self.line = 1
|
|
def skip_space(self):
|
|
while self.pos < self.end:
|
|
c = self.src[self.pos]
|
|
if not c.isspace():
|
|
if c == '/' and self.pos < self.end - 1: #comments
|
|
if self.src[self.pos + 1] == '/': # // coment
|
|
self.pos += 2
|
|
while self.pos < self.end:
|
|
c = self.src[self.pos]
|
|
if c == '\n':
|
|
break
|
|
self.pos += 1
|
|
if self.pos >= self.end:
|
|
raise PListError(self.line,
|
|
"Reached end of string in comment")
|
|
elif self.src[self.pos + 1] == '*': # /* comment */
|
|
start_line = self.line
|
|
self.pos += 2
|
|
while self.pos < self.end:
|
|
c = self.src[self.pos]
|
|
if c == '\n':
|
|
self.line += 1
|
|
elif (c == '*' and self.pos < self.end - 1
|
|
and self.src[self.pos + 1] == '/'):
|
|
self.pos += 1
|
|
break
|
|
self.pos += 1
|
|
if self.pos >= self.end:
|
|
raise PListError(start_line,
|
|
"Reached end of string in comment")
|
|
else:
|
|
return True
|
|
else:
|
|
return True
|
|
if c == '\n':
|
|
self.line += 1
|
|
self.pos += 1
|
|
raise PListError(self.line, "Reached end of string")
|
|
def parse_quoted_string(self):
|
|
start_line = self.line
|
|
long_string = False
|
|
escaped = 0
|
|
shrink = 0
|
|
hexa = False
|
|
self.pos += 1
|
|
start = self.pos
|
|
if (self.pos < self.end - 1 and self.src[self.pos] == '"'
|
|
and self.src[self.pos + 1] == '"'):
|
|
self.pos += 2
|
|
long_string = True
|
|
start += 2
|
|
while self.pos < self.end:
|
|
c = self.src[self.pos]
|
|
if escaped:
|
|
if escaped == 1 and c == '0':
|
|
escaped += 1
|
|
hexa = False
|
|
elif escaped > 1:
|
|
if escaped == 2 and c == 'x':
|
|
hexa = True
|
|
shring += 1
|
|
escaped += 1
|
|
elif hex and c.isxdigit():
|
|
shrink += 1
|
|
escaped += 1
|
|
elif c in range(0, 8):
|
|
shrink += 1
|
|
escaped += 1
|
|
else:
|
|
self.pos -= 1
|
|
escaped = 0
|
|
else:
|
|
escaped = 0
|
|
else:
|
|
if c == '\\':
|
|
escaped = 1
|
|
shrink += 1
|
|
elif (c == '"'
|
|
and (not long_string
|
|
or (self.pos < self.end - 2
|
|
and self.src[self.pos + 1] == '"'
|
|
and self.src[self.pos + 2] == '"'))):
|
|
break
|
|
if c == '\n':
|
|
self.line += 1
|
|
self.pos += 1
|
|
if self.pos >= self.end:
|
|
raise PListError(start_line,
|
|
"Reached end of string while parsing quoted string")
|
|
if self.pos - start - shrink == 0:
|
|
return ""
|
|
s = self.src[start:self.pos]
|
|
self.pos += 1
|
|
if long_string:
|
|
self.pos += 2
|
|
return eval('"""' + s + '"""')
|
|
def parse_unquoted_string(self):
|
|
start = self.pos
|
|
while self.pos < self.end:
|
|
if self.src[self.pos] not in quotables:
|
|
break
|
|
self.pos += 1
|
|
return self.src[start:self.pos]
|
|
def parse_data(self):
|
|
start_line = self.line
|
|
self.pos += 1
|
|
start = self.pos
|
|
nibbles = 0
|
|
while self.pos < self.end:
|
|
if self.src[self.pos].isxdigit:
|
|
nibbles += 1
|
|
self.pos += 1
|
|
continue
|
|
if self.src[self.pos] == '>':
|
|
if nibbles & 1:
|
|
raise PListError(self.line,
|
|
"Invalid data, missing nibble")
|
|
s = self.src[start:self.pos]
|
|
self.pos += 1
|
|
return binascii.a2b_hex(s)
|
|
raise PListError(self.line,
|
|
"Invalid character in data")
|
|
raise PListError(start_line,
|
|
"Reached end of string while parsing data")
|
|
def parse(self):
|
|
self.skip_space()
|
|
if self.src[self.pos] == '{':
|
|
item = {}
|
|
self.pos += 1
|
|
while self.skip_space() and self.src[self.pos] != '}':
|
|
key = self.parse()
|
|
if type(key) != str:
|
|
raise PListError(self.line,
|
|
"Key is not a string")
|
|
self.skip_space()
|
|
if self.src[self.pos] != '=':
|
|
raise PListError(self.line,
|
|
"Unexpected character (expected '=')")
|
|
self.pos += 1
|
|
value = self.parse()
|
|
self.skip_space()
|
|
if self.src[self.pos] == ';':
|
|
self.pos += 1
|
|
elif self.src[self.pos] != '}':
|
|
print(self.src[self.pos])
|
|
raise PListError(self.line,
|
|
"Unexpected character (wanted ';' or '}')")
|
|
item[key] = value
|
|
if self.pos >= self.end:
|
|
raise PListError(self.line,
|
|
"Unexpected end of string when parsing dictionary")
|
|
self.pos += 1
|
|
return item
|
|
elif self.src[self.pos] == '(':
|
|
item = []
|
|
self.pos += 1
|
|
while self.skip_space() and self.src[self.pos] != ')':
|
|
value = self.parse()
|
|
self.skip_space()
|
|
if self.src[self.pos] == ',':
|
|
self.pos += 1
|
|
elif self.src[self.pos] != ')':
|
|
raise PListError(self.line,
|
|
"Unexpected character (wanted ',' or ')')")
|
|
item.append(value)
|
|
self.pos += 1
|
|
return item
|
|
elif self.src[self.pos] == '<':
|
|
return self.parse_data()
|
|
elif self.src[self.pos] == '"':
|
|
return self.parse_quoted_string()
|
|
else:
|
|
return self.parse_unquoted_string()
|
|
def write_string(self, item):
|
|
quote = False
|
|
for i in item:
|
|
if i not in quotables:
|
|
quote = True
|
|
break
|
|
if quote:
|
|
item = repr(item)
|
|
# repr uses ', we want "
|
|
item = '"' + item[1:-1].replace('"', '\\"') + '"'
|
|
self.data.append(item)
|
|
def write_item(self, item, level):
|
|
if type(item) == dict:
|
|
if not item:
|
|
self.data.append("{ }")
|
|
return
|
|
self.data.append("{\n")
|
|
for i in item.items():
|
|
self.data.append('\t' * (level + 1))
|
|
self.write_string(i[0])
|
|
self.data.append(' = ')
|
|
self.write_item(i[1], level + 1)
|
|
self.data.append(';\n')
|
|
self.data.append('\t' * (level))
|
|
self.data.append("}")
|
|
elif type(item) in(list, tuple):
|
|
if not item:
|
|
self.data.append("( )")
|
|
return
|
|
self.data.append("(\n")
|
|
for n, i in enumerate(item):
|
|
self.data.append('\t' * (level + 1))
|
|
self.write_item(i, level + 1)
|
|
if n < len(item) - 1:
|
|
self.data.append(',\n')
|
|
self.data.append('\n')
|
|
self.data.append('\t' * (level))
|
|
self.data.append(")")
|
|
elif type(item) == bytes:
|
|
self.data.append('<')
|
|
self.data.append(binascii.b2a_hex(item))
|
|
self.data.append('>')
|
|
elif type(item) == str:
|
|
self.write_string(item)
|
|
elif type(item) in [int, float]:
|
|
self.write_string(str(item))
|
|
else:
|
|
raise PListError(0, f"unsupported type {type(item)}")
|
|
def write(self, item):
|
|
self.data = []
|
|
self.write_item(item, 0)
|
|
return ''.join(self.data)
|