# HG changeset patch # User Goffi # Date 1588518099 -7200 # Node ID 85c9cfcd4f5e87103aa434d4d17a87e318f39d26 # Parent 2eeca6fd08f79c60fa938f99e00682b9c1bf4a08 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 diff -r 2eeca6fd08f7 -r 85c9cfcd4f5e sat/tools/common/template.py --- a/sat/tools/common/template.py Sun May 03 13:40:04 2020 +0200 +++ b/sat/tools/common/template.py Sun May 03 17:01:39 2020 +0200 @@ -24,6 +24,7 @@ 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 @@ 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 @@ 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 @@ 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 ///filename, then ///filename anf finally // (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/" - @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 @@ 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 @@ 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 @@ 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 @@ 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