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 ...@@ -30,7 +30,6 @@ import json
import zc.buildout import zc.buildout
from jinja2 import Environment, StrictUndefined, \ from jinja2 import Environment, StrictUndefined, \
BaseLoader, TemplateNotFound, PrefixLoader BaseLoader, TemplateNotFound, PrefixLoader
from contextlib import contextmanager
DEFAULT_CONTEXT = {x.__name__: x for x in ( DEFAULT_CONTEXT = {x.__name__: x for x in (
abs, all, any, bin, bool, callable, chr, complex, dict, divmod, abs, all, any, bin, bool, callable, chr, complex, dict, divmod,
...@@ -48,17 +47,6 @@ _buildout_safe_dumps = getattr(zc.buildout.buildout, 'dumps', None) ...@@ -48,17 +47,6 @@ _buildout_safe_dumps = getattr(zc.buildout.buildout, 'dumps', None)
DUMPS_KEY = 'dumps' DUMPS_KEY = 'dumps'
DEFAULT_IMPORT_DELIMITER = '/' 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): def getKey(expression, buildout, _, options):
section, entry = expression.split(':') section, entry = expression.split(':')
if section: if section:
...@@ -141,25 +129,10 @@ LOADER_TYPE_DICT = { ...@@ -141,25 +129,10 @@ LOADER_TYPE_DICT = {
} }
class Recipe(object): class Recipe(object):
mode = 0777 # BBB: 0666 may have been a better default value
umask = None
def __init__(self, buildout, name, options): def __init__(self, buildout, name, options):
template = options['template'] self.buildout = buildout
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.encoding = options.get('encoding', 'utf-8') self.encoding = options.get('encoding', 'utf-8')
source = source.decode(self.encoding)
import_delimiter = options.get('import-delimiter', import_delimiter = options.get('import-delimiter',
DEFAULT_IMPORT_DELIMITER) DEFAULT_IMPORT_DELIMITER)
import_dict = {} import_dict = {}
...@@ -181,6 +154,8 @@ class Recipe(object): ...@@ -181,6 +154,8 @@ class Recipe(object):
else: else:
loader = None loader = None
self.rendered = options['rendered'] self.rendered = options['rendered']
self.template = options['template']
self.md5sum = options.get('md5sum')
extension_list = [x for x in (y.strip() extension_list = [x for x in (y.strip()
for y in options.get('extensions', '').split()) if x] for y in options.get('extensions', '').split()) if x]
self.context = context = DEFAULT_CONTEXT.copy() self.context = context = DEFAULT_CONTEXT.copy()
...@@ -196,42 +171,54 @@ class Recipe(object): ...@@ -196,42 +171,54 @@ class Recipe(object):
context[variable_name] = EXPRESSION_HANDLER[expression_type]( context[variable_name] = EXPRESSION_HANDLER[expression_type](
expression, buildout, name, options) expression, buildout, name, options)
mode = options.get('mode') mode = options.get('mode')
if mode: self.mode = int(mode, 8) if mode else None
self.mode = int(mode, 8) self.env = Environment(
# umask is deprecated, but kept for backward compatibility
umask_value = options.get('umask')
if umask_value:
self.umask = int(umask_value, 8)
env = Environment(
extensions=extension_list, extensions=extension_list,
undefined=StrictUndefined, undefined=StrictUndefined,
loader=loader) 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') self.once = options.get('once')
def install(self): def install(self):
if self.once and os.path.exists(self.once): if self.once and os.path.exists(self.once):
return 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: try:
os.unlink(self.rendered) os.unlink(self.rendered)
except OSError, e: except OSError as e:
if e.errno != errno.ENOENT: if e.errno != errno.ENOENT:
raise raise
with umask(self.umask):
outdir = os.path.dirname(self.rendered) outdir = os.path.dirname(self.rendered)
if outdir and not os.path.exists(outdir): if outdir and not os.path.exists(outdir):
os.makedirs(outdir) os.makedirs(outdir)
# XXX: open doesn't allow providing a filesystem mode, so use mode = self.mode
# os.open and os.fdopen instead. fd = os.open(self.rendered, os.O_CREAT | os.O_EXCL | os.O_WRONLY,
with os.fdopen(os.open(self.rendered, (0777 if rendered.startswith('#!') else 0666)
os.O_CREAT | os.O_EXCL | os.O_WRONLY, if mode is None else 0)
self.mode), 'w') as out: try:
out.write(self.template.render(**self.context) os.write(fd, rendered)
.encode(self.encoding)) if mode is not None:
os.fchmod(fd, mode)
finally:
os.close(fd)
if self.once: if self.once:
open(self.once, 'w').close() open(self.once, 'w').close()
return 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