| | 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 | }}} |