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