151 lines
4.5 KiB
Python
151 lines
4.5 KiB
Python
import ast
|
|
import os
|
|
import re
|
|
|
|
from django.utils.translation import ugettext_lazy as _
|
|
from django.utils.functional import Promise
|
|
|
|
from orchestra.utils.paths import get_project_dir
|
|
|
|
|
|
class Remove(object):
|
|
""" used to signal a setting remove """
|
|
pass
|
|
|
|
|
|
def get_settings_file():
|
|
return os.path.join(get_project_dir(), 'settings.py')
|
|
|
|
|
|
def _find_updates(changes, settings_file):
|
|
""" find all updates needed for applying changes on settings_file content """
|
|
with open(settings_file, 'rb') as handler:
|
|
p = ast.parse(handler.read())
|
|
updates = []
|
|
for elem in p.body:
|
|
if updates and updates[-1][-1] is None:
|
|
updates[-1][-1] = elem.lineno-1
|
|
targets = getattr(elem, 'targets', None)
|
|
if targets:
|
|
var_name = targets[0].id
|
|
if var_name in changes:
|
|
updates.append([var_name, elem.lineno, None])
|
|
return updates
|
|
|
|
|
|
class LazyUgettextRepr(object):
|
|
def __init__(self, value):
|
|
self.value = value
|
|
|
|
def __repr__(self):
|
|
return '_("%s")' % self.value
|
|
|
|
def __len__(self):
|
|
return len(repr(self.value))
|
|
|
|
|
|
class NotSupported(object):
|
|
def __repr__(self):
|
|
return 'Serialization not supported'
|
|
|
|
def __len__(self):
|
|
return 0
|
|
|
|
|
|
def get_eval_context():
|
|
return {
|
|
'NotSupported': NotSupported,
|
|
'_': _,
|
|
}
|
|
|
|
|
|
def serialize(obj, init=True):
|
|
if isinstance(obj, NotSupported):
|
|
return obj
|
|
elif isinstance(obj, Promise):
|
|
_obj = LazyUgettextRepr(obj)
|
|
elif isinstance(obj, dict):
|
|
_obj = {}
|
|
for name, value in obj.items():
|
|
name = serialize(name, init=False)
|
|
value = serialize(value, init=False)
|
|
if isinstance(name, NotSupported) or isinstance(value, NotSupported):
|
|
return NotSupported()
|
|
_obj[name] = value
|
|
elif isinstance(obj, (tuple, list)):
|
|
_obj = []
|
|
for nested in obj:
|
|
nested = serialize(nested, init=False)
|
|
if isinstance(nested, NotSupported):
|
|
return nested
|
|
_obj.append(nested)
|
|
_obj = type(obj)(_obj)
|
|
elif isinstance(obj, (str, bool, int, float)):
|
|
_obj = obj
|
|
else:
|
|
_obj = NotSupported()
|
|
return repr(_obj) if init else _obj
|
|
|
|
|
|
def _format_setting(name, value):
|
|
if isinstance(value, Remove):
|
|
return ""
|
|
value = serialize(eval(value), get_eval_context())
|
|
return "{name} = {value}".format(name=name, value=value)
|
|
|
|
|
|
def apply(changes, settings_file=get_settings_file()):
|
|
""" returns settings_file content with applied changes """
|
|
updates = _find_updates(changes, settings_file)
|
|
content = []
|
|
inside = False
|
|
lineno = None
|
|
if updates:
|
|
name, lineno, end = updates.pop(0)
|
|
# update existing variable declarations
|
|
with open(settings_file, 'r') as handler:
|
|
for num, line in enumerate(handler.readlines(), 1):
|
|
line = line.rstrip()
|
|
if num == lineno:
|
|
value = changes.pop(name)
|
|
line = _format_setting(name, value)
|
|
if line:
|
|
content.append(line)
|
|
inside = True
|
|
comments = []
|
|
lastend = end
|
|
try:
|
|
name, lineno, end = updates.pop(0)
|
|
except IndexError:
|
|
if lastend is None:
|
|
break
|
|
if not inside:
|
|
content.append(line)
|
|
else:
|
|
# Discard lines since variable will be replaced
|
|
# But save comments and blank lines
|
|
if re.match(r'^\s*(#.*)*\s*$', line):
|
|
comments.append(line)
|
|
else:
|
|
comments = []
|
|
# End of variable declaration
|
|
if num == lastend:
|
|
content.extend(comments)
|
|
inside = False
|
|
|
|
# insert new variables at the end of file
|
|
for name, value in changes.items():
|
|
content.append(_format_setting(name, value))
|
|
return '\n'.join(content)
|
|
|
|
|
|
def save(changes, settings_file=get_settings_file(), backup=True):
|
|
""" apply changes to project.settings file, saving a backup """
|
|
new_settings = apply(changes, settings_file)
|
|
tmp_settings_file = settings_file + '.tmp'
|
|
with open(tmp_settings_file, 'w') as handle:
|
|
handle.write(new_settings)
|
|
if backup:
|
|
os.rename(settings_file, settings_file + '.backup')
|
|
os.rename(tmp_settings_file, settings_file)
|