Skip to content

Commit 7c48568

Browse files
committed
Initial commit.
1 parent 825df1e commit 7c48568

File tree

1 file changed

+236
-0
lines changed

1 file changed

+236
-0
lines changed

valve-keyvalues-python/_init_.py

+236
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
__author__ = "Jiri Novotny"
2+
__version__ = "1.0.0"
3+
4+
class KeyValues(dict):
5+
"""
6+
Class for manipulation with Valve KeyValue (KV) files (VDF format). Parses the KV file to object with dict interface.
7+
Allows to write objects with dict interface to KV files.
8+
"""
9+
10+
__re = __import__('re')
11+
__sys = __import__('sys')
12+
__OrderedDict = __import__('collections').OrderedDict
13+
__regexs = {
14+
"key": __re.compile(r"""(['"])(?P<key>((?!\1).)*)\1(?!.)""", __re.I),
15+
"key_value": __re.compile(r"""(['"])(?P<key>((?!\1).)*)\1(\s+|)['"](?P<value>((?!\1).)*)\1""", __re.I)
16+
}
17+
18+
def __init__(self, mapper=None, filename=None, encoding="utf-8", mapper_type=__OrderedDict, key_modifier=None, key_sorter=None):
19+
"""
20+
:param mapper: initialize with own dict-like mapper
21+
:param filename: filename of KV file, which will be parsed to dict structure. Mapper param must not be specified when using this param!
22+
:param encoding: KV file encoding. Default: 'utf-8'
23+
:param mapper_type: which mapper will be used for storing KV. It must have the dict interface, i.e. allow to do the 'mapper[key] = value action'.
24+
default: 'collections.OrderedDict'
25+
For example you can use the 'dict' type.
26+
:param key_modifier: function for modifying the keys, e.g. the function 'string.lower' will make all the keys lower
27+
:param key_sorter: function for sorting the keys when dumping/writing/str, e.g. using the function 'sorted' will show KV keys in alphabetical order
28+
"""
29+
30+
self.__sys.setrecursionlimit(100000)
31+
self.mapper_type = type(mapper) if mapper else mapper_type
32+
self.key_modifier = key_modifier
33+
self.key_sorter = key_sorter
34+
self.encoding = encoding
35+
36+
if not mapper and not filename:
37+
self.mapper = mapper_type()
38+
return
39+
40+
if mapper:
41+
self.mapper = mapper
42+
return
43+
44+
if type(filename) == str:
45+
self.parse(filename)
46+
else:
47+
raise Exception("'filename' argument must be string!")
48+
49+
def __setitem__(self, key, item):
50+
self.mapper[key] = item
51+
52+
def __getitem__(self, key):
53+
return self.mapper[key]
54+
55+
def __repr__(self):
56+
#return repr(self.mapper)
57+
return self.dump(self.mapper)
58+
59+
def __len__(self):
60+
return len(self.mapper)
61+
62+
def __delitem__(self, key):
63+
del self.mapper[key]
64+
65+
def clear(self):
66+
return self.mapper.clear()
67+
68+
def copy(self):
69+
"""
70+
:return: mapper of KeyValues
71+
"""
72+
return self.mapper.copy()
73+
74+
def has_key(self, k):
75+
return self.mapper.has_key(k)
76+
77+
def pop(self, k, d=None):
78+
return self.mapper.pop(k, d)
79+
80+
def update(self, *args, **kwargs):
81+
return self.mapper.update(*args, **kwargs)
82+
83+
def keys(self):
84+
return self.mapper.keys()
85+
86+
def values(self):
87+
return self.mapper.values()
88+
89+
def items(self):
90+
return self.mapper.items()
91+
92+
def pop(self, *args):
93+
return self.mapper.pop(*args)
94+
95+
def __cmp__(self, dict):
96+
return cmp(self.mapper, dict)
97+
98+
def __contains__(self, item):
99+
return item in self.mapper
100+
101+
def __iter__(self):
102+
return iter(self.mapper)
103+
104+
def __unicode__(self):
105+
return unicode(repr(self.mapper))
106+
107+
def __str__(self):
108+
return self.dump()
109+
110+
def __key_modifier(self, key, key_modifier):
111+
"""
112+
Modifies the key string using the 'key_modifier' function.
113+
114+
:param key:
115+
:param key_modifier:
116+
:return:
117+
"""
118+
119+
key_modifier = key_modifier or self.key_modifier
120+
121+
if key_modifier:
122+
return key_modifier(key)
123+
else:
124+
return key
125+
126+
def __parse(self, lines, mapper_type, i=0, key_modifier=None):
127+
"""
128+
Recursively maps the KeyValues from list of file lines.
129+
130+
:param lines:
131+
:param mapper_type:
132+
:param i:
133+
:param key_modifier:
134+
:return:
135+
"""
136+
137+
key = False
138+
_mapper = mapper_type()
139+
140+
try:
141+
while i < len(lines):
142+
if lines[i].startswith("{"):
143+
if not key:
144+
raise Exception("'{{' found without key at line {}".format(i + 1))
145+
_mapper[key], i = self.__parse(lines, i=i+1, mapper_type=mapper_type, key_modifier=key_modifier)
146+
continue
147+
elif lines[i].startswith("}"):
148+
return _mapper, i + 1
149+
elif self.__re.match(self.__regexs["key"], lines[i]):
150+
key = self.__key_modifier(self.__re.search(self.__regexs["key"], lines[i]).group("key"), key_modifier)
151+
i += 1
152+
continue
153+
elif self.__re.match(self.__regexs["key_value"], lines[i]):
154+
groups = self.__re.search(self.__regexs["key_value"], lines[i])
155+
_mapper[self.__key_modifier(groups.group("key"), key_modifier)] = groups.group("value")
156+
i += 1
157+
elif self.__re.match(self.__regexs["key_value"], lines[i] + lines[i+1]):
158+
groups = self.__re.search(self.__regexs["key_value"], lines[i] + " " + lines[i+1])
159+
_mapper[self.__key_modifier(groups.group("key"), key_modifier)] = groups.group("value")
160+
i += 1
161+
else:
162+
i += 1
163+
except IndexError:
164+
pass
165+
166+
return _mapper
167+
168+
def parse(self, filename, encoding="utf-8", mapper_type=__OrderedDict, key_modifier=None):
169+
"""
170+
Parses the KV file so this instance can be accessed by dict interface.
171+
172+
:param filename: name of KV file
173+
:param encoding: KV file encoding. Default: 'utf-8'
174+
:param mapper_type: which mapper will be used for storing KV. It must have the dict interface, i.e. allow to do the 'mapper[key] = value action'.
175+
default: 'collections.OrderedDict'
176+
For example you can use the 'dict' type.
177+
This will override the instance's 'mapper_type' if specified during instantiation.
178+
:param key_modifier: function for modifying the keys, e.g. the function 'string.lower' will make all the keys lower.
179+
This will override the instance's 'key_modifier' if specified during instantiation.
180+
"""
181+
182+
with open(filename, mode="r", encoding=encoding) as f:
183+
self.mapper = self.__parse([line.strip() for line in f.readlines()],
184+
mapper_type=mapper_type or self.mapper_type,
185+
key_modifier=key_modifier or self.key_modifier)
186+
187+
def __tab(self, string, level, quotes=False):
188+
if quotes:
189+
return '{}"{}"'.format(level * "\t", string)
190+
else:
191+
return '{}{}'.format(level * "\t", string)
192+
193+
def __dump(self, mapper, key_sorter=None, level=0):
194+
string = ""
195+
196+
if key_sorter:
197+
keys = key_sorter(mapper.keys())
198+
else:
199+
keys = mapper.keys()
200+
201+
for key in keys:
202+
string += self.__tab(key, level, quotes=True)
203+
if type(mapper[key]) == str:
204+
string += '\t "{}"\n'.format(mapper[key])
205+
else:
206+
string += "\n" + self.__tab("{\n", level)
207+
string += self.__dump(mapper[key], key_sorter=key_sorter, level=level+1)
208+
string += self.__tab("}\n", level)
209+
210+
return string
211+
212+
def dump(self, mapper=None, key_sorter=None):
213+
"""
214+
Dumps the KeyValues mapper to string.
215+
216+
:param mapper: you can dump your own object with dict interface
217+
:param key_sorter: function for sorting the keys when dumping/writing/str, e.g. using the function 'sorted' will show KV in alphabetical order.
218+
This will override the instance's 'key_sorter' if specified during instantiation.
219+
:return: string
220+
"""
221+
222+
return self.__dump(mapper=mapper or self.mapper, key_sorter=key_sorter or self.key_sorter)
223+
224+
def write(self, filename, encoding="utf-8", mapper=None, key_sorter=None):
225+
"""
226+
Writes the KeyValues to file.
227+
228+
:param filename: output KV file name
229+
:param encoding: output KV file encoding. Default: 'utf-8'
230+
:param mapper: you can write your own object with dict interface
231+
:param key_sorter: key_sorter: function for sorting the keys when dumping/writing/str, e.g. using the function 'sorted' will show KV in alphabetical order.
232+
This will override the instance's 'key_sorter' if specified during instantiation.
233+
"""
234+
235+
with open(filename, mode="w", encoding=encoding) as f:
236+
f.write(self.dump(mapper=mapper or self.mapper, key_sorter=key_sorter or self.key_sorter))

0 commit comments

Comments
 (0)