Commit 6219b5dc authored by Julien Muchembled's avatar Julien Muchembled

jinja2: read template at install/update and fix 'mode' option

__init__ should only check options. By doing the actual work in install/update,
it is now possible to use a template that is produced by another part.

'mode' was implemented like 'umask' but it has never been used this way,
and the 'umask' option is deprecated:
- remove support for unused 'umask' option
- change 'mode' to actually apply the wanted mode, regardless the current umask
- the default mode changes: if 'mode' is not given, the resulting file is only
  executable (actually 0777 & ~umask) if the content starts with '#!',
  else its mode is: 0666 & ~umask
parent 135197de
......@@ -30,7 +30,6 @@ import json
import zc.buildout
from jinja2 import Environment, StrictUndefined, \
BaseLoader, TemplateNotFound, PrefixLoader
from contextlib import contextmanager
DEFAULT_CONTEXT = {x.__name__: x for x in (
abs, all, any, bin, bool, callable, chr, complex, dict, divmod,
......@@ -48,17 +47,6 @@ _buildout_safe_dumps = getattr(zc.buildout.buildout, 'dumps', None)
DUMPS_KEY = 'dumps'
DEFAULT_IMPORT_DELIMITER = '/'
@contextmanager
def umask(mask):
if mask is None:
yield
return
original = os.umask(mask)
try:
yield original
finally:
os.umask(original)
def getKey(expression, buildout, _, options):
section, entry = expression.split(':')
if section:
......@@ -141,25 +129,10 @@ LOADER_TYPE_DICT = {
}
class Recipe(object):
mode = 0777 # BBB: 0666 may have been a better default value
umask = None
def __init__(self, buildout, name, options):
template = options['template']
if template.startswith('inline:'):
source = template[7:].lstrip('\r\n')
template = '<inline>'
else:
template = zc.buildout.download.Download(
buildout['buildout'],
hash_name=True,
)(
template,
md5sum=options.get('md5sum'),
)[0]
source = open(template).read()
self.buildout = buildout
self.encoding = options.get('encoding', 'utf-8')
source = source.decode(self.encoding)
import_delimiter = options.get('import-delimiter',
DEFAULT_IMPORT_DELIMITER)
import_dict = {}
......@@ -181,6 +154,8 @@ class Recipe(object):
else:
loader = None
self.rendered = options['rendered']
self.template = options['template']
self.md5sum = options.get('md5sum')
extension_list = [x for x in (y.strip()
for y in options.get('extensions', '').split()) if x]
self.context = context = DEFAULT_CONTEXT.copy()
......@@ -196,42 +171,54 @@ class Recipe(object):
context[variable_name] = EXPRESSION_HANDLER[expression_type](
expression, buildout, name, options)
mode = options.get('mode')
if mode:
self.mode = int(mode, 8)
# umask is deprecated, but kept for backward compatibility
umask_value = options.get('umask')
if umask_value:
self.umask = int(umask_value, 8)
env = Environment(
self.mode = int(mode, 8) if mode else None
self.env = Environment(
extensions=extension_list,
undefined=StrictUndefined,
loader=loader)
# For overriding from_string method of jinja2
self.template = env.template_class.from_code(env,
env.compile(source, filename=template),
env.make_globals(None), None)
self.once = options.get('once')
def install(self):
if self.once and os.path.exists(self.once):
return
# Unlink any existing file, so umask is always applied.
template = self.template
if template.startswith('inline:'):
source = template[7:].lstrip('\r\n')
template = '<inline>'
else:
template = zc.buildout.download.Download(
self.buildout['buildout'],
hash_name=True,
)(
template,
md5sum=self.md5sum,
)[0]
with open(template, 'rb') as f:
source = f.read().decode(self.encoding)
env = self.env
template = env.template_class.from_code(env,
env.compile(source, filename=template),
env.make_globals(None), None)
rendered = template.render(**self.context).encode(self.encoding)
# Unlink any existing file so that umask applies.
try:
os.unlink(self.rendered)
except OSError, e:
except OSError as e:
if e.errno != errno.ENOENT:
raise
with umask(self.umask):
outdir = os.path.dirname(self.rendered)
if outdir and not os.path.exists(outdir):
os.makedirs(outdir)
# XXX: open doesn't allow providing a filesystem mode, so use
# os.open and os.fdopen instead.
with os.fdopen(os.open(self.rendered,
os.O_CREAT | os.O_EXCL | os.O_WRONLY,
self.mode), 'w') as out:
out.write(self.template.render(**self.context)
.encode(self.encoding))
mode = self.mode
fd = os.open(self.rendered, os.O_CREAT | os.O_EXCL | os.O_WRONLY,
(0777 if rendered.startswith('#!') else 0666)
if mode is None else 0)
try:
os.write(fd, rendered)
if mode is not None:
os.fchmod(fd, mode)
finally:
os.close(fd)
if self.once:
open(self.once, 'w').close()
return
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment