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