tools (common/template): theme settings with possibility to disable default fallback for CSS:

- themes can now have a `settings.json` file at their root to modify the renderer
  behaviour
- the `css_default_fallback` boolean (true by default) can be used to disable the fallback
  mechanism when CSS files are checked. Useful when a theme doesn't follow the CSS naming of
  default theme.
- introduced some type hints
This commit is contained in:
Goffi 2020-05-03 17:01:39 +02:00
commit 19a9e38983

View file

@ -24,6 +24,7 @@ import re
import json
from pathlib import Path
from collections import namedtuple
from typing import Optional, List, Tuple
from xml.sax.saxutils import quoteattr
from babel import support
from babel import Locale
@ -159,18 +160,22 @@ class TemplateLoader(jinja2.BaseLoader):
return TemplateData(site, theme, template_path)
@staticmethod
def getSitesAndThemes(site, theme):
def getSitesAndThemes(
site: str,
theme: str,
default_fallback: bool=True
) -> List[Tuple[str, str]]:
"""Get sites and themes to check for template/file
Will add default theme and default site in search list when suitable
@param site(unicode): site requested
@param theme(unicode): theme requested
@return (list[tuple[unicode, unicode]]): site and theme couples to check
@param site: site requested
@param theme: theme requested
@return: site and theme couples to check
"""
sites_and_themes = [[site, theme]]
if theme != C.TEMPLATE_THEME_DEFAULT:
if theme != C.TEMPLATE_THEME_DEFAULT and default_fallback:
sites_and_themes.append([site, C.TEMPLATE_THEME_DEFAULT])
if site:
if site and default_fallback:
# the site is not the default one, so we add default at the end
sites_and_themes.append(['', C.TEMPLATE_THEME_DEFAULT])
return sites_and_themes
@ -383,7 +388,22 @@ class Renderer(object):
if not p.is_dir():
continue
log.debug(f"theme found for {site or 'default site'}: {p.name}")
theme_data = self.sites_themes.setdefault(site, {})[p.name] = {'path': p}
theme_data = self.sites_themes.setdefault(site, {})[p.name] = {
'path': p,
'settings': {}}
theme_settings = p / "settings.json"
if theme_settings.is_file:
try:
with theme_settings.open() as f:
settings = json.load(f)
except Exception as e:
log.warning(_(
"Can't load theme settings at {path}").format(
path=theme_settings))
else:
log.debug(
f"found settings for theme {p.name!r} at {theme_settings}")
theme_data['settings'] = settings
browser_path = p / BROWSER_DIR
if browser_path.is_dir():
theme_data['browser_path'] = browser_path
@ -548,22 +568,30 @@ class Renderer(object):
except KeyError:
raise exceptions.NotFound(f"no theme found for {site_name}")
def getStaticPath(self, template_data, filename):
def getStaticPath(
self,
template_data: TemplateData,
filename: str,
default_fallback: bool=True
) -> Optional[TemplateData]:
"""Retrieve path of a static file if it exists with current theme or default
File will be looked at <site_root_dir>/<theme_dir>/<static_dir>/filename,
then <site_root_dir>/<default_theme_dir>/<static_dir>/filename anf finally
<default_site>/<default_theme_dir>/<static_dir> (i.e. sat_templates).
In case of absolue URL, base dir of template is used as base. For instance if
In case of absolute URL, base dir of template is used as base. For instance if
template is an absolute template to "/some/path/template.html", file will be
checked at "/some/path/<filename>"
@param template_data(TemplateData): data of current template
@return (TemplateData, None): built template data instance where .path is
@param template_data: data of current template
@param filename: name of the file to retrieve
@param default_fallback: if True, default theme will be checked if the file is
not found in current theme, then default site with default theme will be used.
@return: built template data instance where .path is
the relative path to the file, from theme root dir.
None if not found.
"""
if template_data.site is None:
# we have and absolue path
# we have an absolue path
if (not template_data.theme is None
or not template_data.path.startswith('/')):
raise exceptions.InternalError(
@ -576,7 +604,8 @@ class Renderer(object):
return None
sites_and_themes = TemplateLoader.getSitesAndThemes(template_data.site,
template_data.theme)
template_data.theme,
default_fallback)
for site, theme in sites_and_themes:
site_root_dir = self.sites_paths[site]
relative_path = os.path.join(C.TEMPLATE_STATIC_DIR, filename)
@ -587,19 +616,28 @@ class Renderer(object):
return None
def _appendCSSPaths(self, template_data, css_files, css_files_noscript, name_root):
def _appendCSSPaths(
self,
template_data: TemplateData,
css_files: list,
css_files_noscript: list,
name_root: str,
default_fallback: bool
) -> None:
"""Append found css to css_files and css_files_noscript
@param css_files(list): list to fill of relative path to found css file
@param css_files_noscript(list): list to fill of relative path to found css file
@param css_files: list to fill of relative path to found css file
@param css_files_noscript: list to fill of relative path to found css file
with "_noscript" suffix
"""
name = name_root + ".css"
css_path = self.getStaticPath(template_data, name)
css_path = self.getStaticPath(template_data, name, default_fallback)
if css_path is not None:
css_files.append(self.getFrontURL(css_path))
noscript_name = name_root + "_noscript.css"
noscript_path = self.getStaticPath(template_data, noscript_name)
noscript_path = self.getStaticPath(
template_data, noscript_name, default_fallback)
if noscript_path is not None:
css_files_noscript.append(self.getFrontURL(noscript_path))
@ -624,6 +662,9 @@ class Renderer(object):
else default/static/blog_articles.css (if it exists too)
and for each found files, if same file with _noscript suffix exists, it is put
in noscript list (for instance (some_theme/static/styles_noscript.css)).
The behaviour may be changed with theme settings: if "css_default_fallback" is
False, only CSS from the theme is returned if it exists, default CSS is never
used.
@param template_data(TemplateData): data of the current template
@return (tuple[list[unicode], list[unicode]]): a tuple with:
- front URLs of CSS files to use
@ -634,17 +675,27 @@ class Renderer(object):
css_files_noscript = []
path_elems = template_data.path.split('/')
path_elems[-1] = os.path.splitext(path_elems[-1])[0]
site = template_data.site
if site is None:
# absolute path
default_fallback = True
else:
default_fallback = (
self.sites_themes[site][template_data.theme]['settings']
).get('css_default_fallback', True)
css_path = self.getStaticPath(template_data, 'fonts.css')
css_path = self.getStaticPath(template_data, 'fonts.css', default_fallback)
if css_path is not None:
css_files.append(self.getFrontURL(css_path))
for name_root in ('styles', 'styles_extra', 'highlight'):
self._appendCSSPaths(template_data, css_files, css_files_noscript, name_root)
self._appendCSSPaths(
template_data, css_files, css_files_noscript, name_root, default_fallback)
for idx in range(len(path_elems)):
name_root = "_".join(path_elems[:idx+1])
self._appendCSSPaths(template_data, css_files, css_files_noscript, name_root)
self._appendCSSPaths(
template_data, css_files, css_files_noscript, name_root, default_fallback)
return css_files, css_files_noscript