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