# Programmer: limodou
# E-mail: limodou@gmail.com
#
# Copyleft 2006 limodou
#
# Distributed under the terms of the GPL (GNU Public License)
#
# NewEdit is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
# version 0.1
# This program is used for parsing settings.py. So it has many limits.
# It has some features:
# * Remaining comments in settings, including comments in tuple. And if the
# comments in tuple data type, they should be nearby the first '(' or the
# last ')', other comments will be lost.
# * You can delete a key, but when you save the settings, the key will be
# comment but not be really delete
# * You can also get a comment key, and if you set a new value to it,
# the key will be uncomment. Or you can also remove the key object's delete flag
# to uncomment the key, just like:
#
# obj = ini.get_obj('KEY')
# obj.delete = False
# * If the value is value, DjangoIni will read it as list, so you can deal with
# the key as a list. But when it's saved, it'll be automaticaly converted to tuple,
# just keeping the same type in settings.py
# * The instance of DjangoIni acts as a dict object, so you can do like:
# from DjangoIni import DjangoIni
# ini = DjangoIni('settings.py')
# ini['KEY'] if a key has been delete, so it'll complain a KeyError Exception
# ini.get('KEY', defaultvalue) if a key has been delete, it'll also can be returned
# ini.keys() omit the key deleted
# 'KEY' in ini omit the key deleted
# ini.get_obj('KEY') can also get a delete obj
# ini.save(filename or fileobj) saving the result to file
#
import codecs
import copy
import re
import os
import sys
r_line = re.compile(r'(\w+)\s+=\s*([^#]+)')
class DeleteException(Exception):pass
class Node:
def __init__(self, parent, key, old_value, lines, span=None, new=False, delete=False):
self.parent = parent
self.key = key
self.value = copy.deepcopy(old_value)
self.old_value = copy.deepcopy(old_value)
self.lines = lines
self.delete = delete
self.new = new
self.span = span
def render(self):
if self.lines[0] == -1: #new
if isinstance(self.value, (tuple, list)):
line = []
line.append('%s = (')
for i in self.value:
line.append(' '*4 + repr(i) + ',')
line.append(')')
else:
line = ['%s = %r' % (self.key, self.value)]
else:
if len(self.lines) == 1:
line = self.parent.get_lines(self.lines[0])
b, e = self.span
line = ["%s = %r" % (self.key, self.value) + line[e:]]
else:
b, e = self.lines
line = [self.parent.get_lines(b)]
has_more = False
for j in self.parent.get_lines(b+1, e-1):
if j.strip().startswith('#'):
line.append(j)
else:
has_more = True
break
for i in self.value:
line.append(' '*4 + repr(i) + ',')
line.append(')')
if has_more:
lines = self.parent.get_lines(b+1, e-1)
lines.reverse()
for j in lines:
if j.strip().startswith('#'):
line.insert(-1, j)
else:
break
if self.delete:
if self.new:
return None
else:
return ['#'+x for x in line]
return line
class DjangoIni(object):
def __init__(self, filename='', encoding='utf-8'):
self._items = {}
self._orders = {}
self._id = 0
self._max_id = 99999
self._lines = []
self.filename = filename
self.read(self.filename)
def _add_order(self, key):
self._id += 1
self._orders[key] = self._id
def _add_max_order(self, key):
self._max_id += 1
self._orders[key] = self._max_id
def read(self, filename, encoding='utf-8'):
if not filename:
return
dir = os.path.dirname(filename)
sys.path.insert(0, dir)
mod = __import__(os.path.splitext(os.path.basename(filename))[0])
sys.path.pop(0)
f = codecs.open(filename, encoding=encoding)
i = 0
for line in f:
line = line.rstrip()
self._lines.append(line) #saving all lines
if line.startswith('#'):
deleteflag = True
line = line[1:]
else:
deleteflag = False
b = r_line.search(line)
if b:
self._lines[-1] = line
key, value = b.groups()
lines = [i]
x, y = b.span(2)
if deleteflag:
x += 1
value = value.strip()
obj = Node(self, key, value, lines, span=(0, x+len(value)), delete=deleteflag)
self._items[key] = obj
self._add_order(key)
if value == '(':
s = ['(']
while True:
line = f.next().rstrip()
if line.startswith('#') and deleteflag:
line = line[1:]
self._lines.append(line)
i += 1
if line != ')':
t = line.lstrip()
if not t.startswith('#'):
s.append(line)
else:
obj.old_value = s
obj.value = [x.strip()[:-1] for x in s if not x.strip().startswith('#')]
lines.append(i+1)
s.append(')')
value = ''.join(s)
break
else:
if value and value[0] in ("'", '"'):
ch = value[0]
pp = [ch]
line_iter = iter(line[x+1:])
for j in line_iter:
if j == '\\':
pp.append(j)
j = line_iter.next()
pp.append(j)
elif j == ch:
pp.append(j)
break
else:
pp.append(j)
value = ''.join(pp)
obj.span = (0, x + len(value))
if hasattr(mod, key):
obj.value = getattr(mod, key)
else:
obj.value = eval(value)
if isinstance(obj.value, tuple):
obj.value = list(obj.value)
i += 1
def get_lines(self, start, end=-1):
if end <= start:
return self._lines[start]
else:
return self._lines[start:end]
def out(self):
a = [(value, key) for key, value in self._orders.items()]
a.sort()
for i, key in a:
print key, self._items[key].value, self._items[key].lines
def __setitem__(self, name, value):
obj = self._items.get(name, None)
if not obj:
obj = Node(self, name, value, [-1], new=True)
self._items[name] = obj
self._add_max_order(name)
else:
if obj.delete:
obj.delete = False
obj.value = value
def __getitem__(self, name):
obj = self._items.get(name, None)
if obj:
if obj.delete:
raise DeleteException, 'The value has been deleted!'
else:
return obj.value
else:
raise KeyError, name
def __delitem__(self, name):
"""set an item's delete flag, so the result will be commented in .py file"""
obj = self._items.get(name, None)
if obj:
obj.delete = True
else:
raise KeyError, name
def get(self, name, defaultvalue):
"""Get an item, but also can get deleted item"""
obj = self._items.get(name, None)
if obj:
return obj.value
else:
return defaultvalue
def get_obj(self, name):
"""Get an item, but also can get deleted item"""
obj = self._items.get(name, None)
if obj:
return obj
else:
raise KeyError, name
def keys(self):
return self._items.keys()
def values(self):
return [obj.value for obj in self.items.values()]
def __contains__(self, name):
return name in self._items
def save(self, filename=None, encoding='utf-8'):
if not filename:
filename = self.filename
a = [(value, key) for key, value in self._orders.items()]
a.sort()
k = 0
s = []
last = []
for i, key in a:
obj = self._items[key]
b = obj.lines[0]
while k < b:
s.append(self.get_lines(k))
k += 1
if len(obj.lines) == 1:
b = obj.lines[0]
if b == -1:
last.extend(obj.render())
else:
s.extend(obj.render())
k = b + 1
else:
b, e = obj.lines
s.extend(obj.render())
k = e
s.extend(last)
if isinstance(filename, (str, unicode)):
f = codecs.open(filename, 'w', encoding)
f.write('\n'.join(s))
f.close()
else:
filename.write('\n'.join(s))
if __name__ == '__main__':
ini = DjangoIni('settings.py')
print ini.keys()
print ini['LANGUAGE_CODE']
ini['ADMINS'].append(('limodou', 'limodou@gmail.com'))
ini['DATABASE_ENGINE'] = ''
ini['DEBUG'] = False
ini['NEW'] = 'This is test'
# ini.save('t.py')
print '-----------------------------------------------'
ini.save(sys.stdout)