| 1 | {{{ |
| 2 | # Programmer: limodou |
| 3 | # E-mail: limodou@gmail.com |
| 4 | # |
| 5 | # Copyleft 2006 limodou |
| 6 | # |
| 7 | # Distributed under the terms of the GPL (GNU Public License) |
| 8 | # |
| 9 | # NewEdit is free software; you can redistribute it and/or modify |
| 10 | # it under the terms of the GNU General Public License as published by |
| 11 | # the Free Software Foundation; either version 2 of the License, or |
| 12 | # (at your option) any later version. |
| 13 | # |
| 14 | # This program is distributed in the hope that it will be useful, |
| 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 17 | # GNU General Public License for more details. |
| 18 | # |
| 19 | # You should have received a copy of the GNU General Public License |
| 20 | # along with this program; if not, write to the Free Software |
| 21 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| 22 | # |
| 23 | # version 0.1 |
| 24 | # This program is used for parsing settings.py. So it has many limits. |
| 25 | # It has some features: |
| 26 | # * Remaining comments in settings, including comments in tuple. And if the |
| 27 | # comments in tuple data type, they should be nearby the first '(' or the |
| 28 | # last ')', other comments will be lost. |
| 29 | # * You can delete a key, but when you save the settings, the key will be |
| 30 | # comment but not be really delete |
| 31 | # * You can also get a comment key, and if you set a new value to it, |
| 32 | # the key will be uncomment. Or you can also remove the key object's delete flag |
| 33 | # to uncomment the key, just like: |
| 34 | # |
| 35 | # obj = ini.get_obj('KEY') |
| 36 | # obj.delete = False |
| 37 | # * If the value is value, DjangoIni will read it as list, so you can deal with |
| 38 | # the key as a list. But when it's saved, it'll be automaticaly converted to tuple, |
| 39 | # just keeping the same type in settings.py |
| 40 | # * The instance of DjangoIni acts as a dict object, so you can do like: |
| 41 | # from DjangoIni import DjangoIni |
| 42 | # ini = DjangoIni('settings.py') |
| 43 | # ini['KEY'] if a key has been delete, so it'll complain a KeyError Exception |
| 44 | # ini.get('KEY', defaultvalue) if a key has been delete, it'll also can be returned |
| 45 | # ini.keys() omit the key deleted |
| 46 | # ini.has_key('KEY') omit the key deleted |
| 47 | # ini.get_obj('KEY') can also get a delete obj |
| 48 | # ini.save(filename or fileobj) saving the result to file |
| 49 | # |
| 50 | |
| 51 | import codecs |
| 52 | import copy |
| 53 | import re |
| 54 | import os |
| 55 | import sys |
| 56 | |
| 57 | r_line = re.compile(r'(\w+)\s+=\s*([^#]+)') |
| 58 | |
| 59 | class DeleteException(Exception):pass |
| 60 | |
| 61 | class Node: |
| 62 | def __init__(self, parent, key, old_value, lines, span=None, new=False, delete=False): |
| 63 | self.parent = parent |
| 64 | self.key = key |
| 65 | self.value = copy.deepcopy(old_value) |
| 66 | self.old_value = copy.deepcopy(old_value) |
| 67 | self.lines = lines |
| 68 | self.delete = delete |
| 69 | self.new = new |
| 70 | self.span = span |
| 71 | |
| 72 | def render(self): |
| 73 | if self.lines[0] == -1: #new |
| 74 | if isinstance(self.value, (tuple, list)): |
| 75 | line = [] |
| 76 | line.append('%s = (') |
| 77 | for i in self.value: |
| 78 | line.append(' '*4 + repr(i) + ',') |
| 79 | line.append(')') |
| 80 | else: |
| 81 | line = ['%s = %r' % (self.key, self.value)] |
| 82 | else: |
| 83 | if len(self.lines) == 1: |
| 84 | line = self.parent.get_lines(self.lines[0]) |
| 85 | b, e = self.span |
| 86 | line = ["%s = %r" % (self.key, self.value) + line[e:]] |
| 87 | else: |
| 88 | b, e = self.lines |
| 89 | line = [self.parent.get_lines(b)] |
| 90 | has_more = False |
| 91 | for j in self.parent.get_lines(b+1, e-1): |
| 92 | if j.strip().startswith('#'): |
| 93 | line.append(j) |
| 94 | else: |
| 95 | has_more = True |
| 96 | break |
| 97 | for i in self.value: |
| 98 | line.append(' '*4 + repr(i) + ',') |
| 99 | line.append(')') |
| 100 | |
| 101 | if has_more: |
| 102 | lines = self.parent.get_lines(b+1, e-1) |
| 103 | lines.reverse() |
| 104 | for j in lines: |
| 105 | if j.strip().startswith('#'): |
| 106 | line.insert(len(line)-1, j) |
| 107 | else: |
| 108 | break |
| 109 | |
| 110 | if self.delete: |
| 111 | if self.new: |
| 112 | return None |
| 113 | else: |
| 114 | return ['#'+x for x in line] |
| 115 | return line |
| 116 | |
| 117 | class DjangoIni(object): |
| 118 | def __init__(self, filename='', encoding='utf-8'): |
| 119 | self._items = {} |
| 120 | self._orders = {} |
| 121 | self._id = 0 |
| 122 | self._max_id = 99999 |
| 123 | self._lines = [] |
| 124 | self.filename = filename |
| 125 | self.read(self.filename) |
| 126 | |
| 127 | def _add_order(self, key): |
| 128 | self._id += 1 |
| 129 | self._orders[key] = self._id |
| 130 | |
| 131 | def _add_max_order(self, key): |
| 132 | self._max_id += 1 |
| 133 | self._orders[key] = self._max_id |
| 134 | |
| 135 | def read(self, filename, encoding='utf-8'): |
| 136 | if not filename: |
| 137 | return |
| 138 | |
| 139 | dir = os.path.dirname(filename) |
| 140 | sys.path.insert(0, dir) |
| 141 | mod = __import__(os.path.splitext(os.path.basename(filename))[0]) |
| 142 | sys.path.pop(0) |
| 143 | f = codecs.open(filename, encoding=encoding) |
| 144 | i = 0 |
| 145 | for line in f: |
| 146 | line = line.rstrip() |
| 147 | self._lines.append(line) #saving all lines |
| 148 | if line.startswith('#'): |
| 149 | deleteflag = True |
| 150 | line = line[1:] |
| 151 | else: |
| 152 | deleteflag = False |
| 153 | b = r_line.search(line) |
| 154 | if b: |
| 155 | self._lines[-1] = line |
| 156 | key, value = b.groups() |
| 157 | lines = [i] |
| 158 | x, y = b.span(2) |
| 159 | if deleteflag: |
| 160 | x = x + 1 |
| 161 | value = value.strip() |
| 162 | obj = Node(self, key, value, lines, span=(0, x+len(value)), delete=deleteflag) |
| 163 | self._items[key] = obj |
| 164 | self._add_order(key) |
| 165 | if value == '(': |
| 166 | s = ['('] |
| 167 | while 1: |
| 168 | line = f.next().rstrip() |
| 169 | if line.startswith('#') and deleteflag: |
| 170 | line = line[1:] |
| 171 | self._lines.append(line) |
| 172 | i += 1 |
| 173 | if line != ')': |
| 174 | t = line.lstrip() |
| 175 | if not t.startswith('#'): |
| 176 | s.append(line) |
| 177 | else: |
| 178 | obj.old_value = s |
| 179 | obj.value = [x.strip()[:-1] for x in s if not x.strip().startswith('#')] |
| 180 | lines.append(i+1) |
| 181 | s.append(')') |
| 182 | value = ''.join(s) |
| 183 | break |
| 184 | else: |
| 185 | if value and value[0] in ("'", '"'): |
| 186 | ch = value[0] |
| 187 | pp = [ch] |
| 188 | line_iter = iter(line[x+1:]) |
| 189 | for j in line_iter: |
| 190 | if j == '\\': |
| 191 | pp.append(j) |
| 192 | j = line_iter.next() |
| 193 | pp.append(j) |
| 194 | elif j == ch: |
| 195 | pp.append(j) |
| 196 | break |
| 197 | else: |
| 198 | pp.append(j) |
| 199 | value = ''.join(pp) |
| 200 | obj.span = (0, x + len(value)) |
| 201 | |
| 202 | if hasattr(mod, key): |
| 203 | obj.value = getattr(mod, key) |
| 204 | else: |
| 205 | obj.value = eval(value) |
| 206 | if isinstance(obj.value, tuple): |
| 207 | obj.value = list(obj.value) |
| 208 | i += 1 |
| 209 | |
| 210 | def get_lines(self, start, end=-1): |
| 211 | if end <= start: |
| 212 | return self._lines[start] |
| 213 | else: |
| 214 | return self._lines[start:end] |
| 215 | |
| 216 | def out(self): |
| 217 | a = [(value, key) for key, value in self._orders.items()] |
| 218 | a.sort() |
| 219 | for i, key in a: |
| 220 | print key, self._items[key].value, self._items[key].lines |
| 221 | |
| 222 | def __setitem__(self, name, value): |
| 223 | obj = self._items.get(name, None) |
| 224 | if not obj: |
| 225 | obj = Node(self, name, value, [-1], new=True) |
| 226 | self._items[name] = obj |
| 227 | self._add_max_order(name) |
| 228 | else: |
| 229 | if obj.delete: |
| 230 | obj.delete = False |
| 231 | obj.value = value |
| 232 | |
| 233 | def __getitem__(self, name): |
| 234 | obj = self._items.get(name, None) |
| 235 | if obj: |
| 236 | if obj.delete: |
| 237 | raise DeleteException, 'The value has been deleted!' |
| 238 | else: |
| 239 | return obj.value |
| 240 | else: |
| 241 | raise KeyError, name |
| 242 | |
| 243 | def __delitem__(self, name): |
| 244 | """set an item's delete flag, so the result will be commented in .py file""" |
| 245 | obj = self._items.get(name, None) |
| 246 | if obj: |
| 247 | obj.delete = True |
| 248 | else: |
| 249 | raise KeyError, name |
| 250 | |
| 251 | def get(self, name, defaultvalue): |
| 252 | """Get an item, but also can get deleted item""" |
| 253 | obj = self._items.get(name, None) |
| 254 | if obj: |
| 255 | return obj.value |
| 256 | else: |
| 257 | return defaultvalue |
| 258 | |
| 259 | def get_obj(self, name): |
| 260 | """Get an item, but also can get deleted item""" |
| 261 | obj = self._items.get(name, None) |
| 262 | if obj: |
| 263 | return obj |
| 264 | else: |
| 265 | raise KeyError, name |
| 266 | |
| 267 | def keys(self): |
| 268 | return self._items.keys() |
| 269 | |
| 270 | def values(self): |
| 271 | return [obj.value for obj in self.items.values()] |
| 272 | |
| 273 | def has_key(self, name): |
| 274 | return self._items.has_key(name) |
| 275 | |
| 276 | def save(self, filename=None, encoding='utf-8'): |
| 277 | if not filename: |
| 278 | filename = self.filename |
| 279 | a = [(value, key) for key, value in self._orders.items()] |
| 280 | a.sort() |
| 281 | |
| 282 | k = 0 |
| 283 | s = [] |
| 284 | last = [] |
| 285 | for i, key in a: |
| 286 | obj = self._items[key] |
| 287 | b = obj.lines[0] |
| 288 | while k < b: |
| 289 | s.append(self.get_lines(k)) |
| 290 | k += 1 |
| 291 | if len(obj.lines) == 1: |
| 292 | b = obj.lines[0] |
| 293 | if b == -1: |
| 294 | last.extend(obj.render()) |
| 295 | else: |
| 296 | s.extend(obj.render()) |
| 297 | k = b + 1 |
| 298 | else: |
| 299 | b, e = obj.lines |
| 300 | s.extend(obj.render()) |
| 301 | k = e |
| 302 | s.extend(last) |
| 303 | |
| 304 | if isinstance(filename, (str, unicode)): |
| 305 | f = codecs.open(filename, 'w', encoding) |
| 306 | f.write('\n'.join(s)) |
| 307 | f.close() |
| 308 | else: |
| 309 | filename.write('\n'.join(s)) |
| 310 | |
| 311 | if __name__ == '__main__': |
| 312 | ini = DjangoIni('settings.py') |
| 313 | print ini.keys() |
| 314 | print ini['LANGUAGE_CODE'] |
| 315 | ini['ADMINS'].append(('limodou', 'limodou@gmail.com')) |
| 316 | ini['DATABASE_ENGINE'] = '' |
| 317 | ini['DEBUG'] = False |
| 318 | ini['NEW'] = 'This is test' |
| 319 | # ini.save('t.py') |
| 320 | print '-----------------------------------------------' |
| 321 | ini.save(sys.stdout) |
| 322 | }}} |