comparison sat/tools/common/template.py @ 3266:8ec5ddb4e759

tools (common/template): list themes and parse their browser data, available through new `getThemesData` method
author Goffi <goffi@goffi.org>
date Fri, 01 May 2020 16:26:39 +0200
parents 559a625a236b
children 2eeca6fd08f7
comparison
equal deleted inserted replaced
3265:1649bbe8d07e 3266:8ec5ddb4e759
1 #!/usr/bin/env python3 1 #!/usr/bin/env python3
2 2
3 3 # SAT: an XMPP client
4 # SAT: a jabber client
5 # Copyright (C) 2009-2020 Jérôme Poisson (goffi@goffi.org) 4 # Copyright (C) 2009-2020 Jérôme Poisson (goffi@goffi.org)
6 5
7 # This program is free software: you can redistribute it and/or modify 6 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as published by 7 # it under the terms of the GNU Affero General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or 8 # the Free Software Foundation, either version 3 of the License, or
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. 17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 18
20 """Template generation""" 19 """Template generation"""
21 20
22 import os.path 21 import os.path
22 import time
23 import re
24 import json
25 from pathlib import Path
23 from collections import namedtuple 26 from collections import namedtuple
27 from xml.sax.saxutils import quoteattr
28 from babel import support
29 from babel import Locale
30 from babel.core import UnknownLocaleError
31 import pygments
32 from pygments import lexers
33 from pygments import formatters
24 from sat.core.constants import Const as C 34 from sat.core.constants import Const as C
25 from sat.core.i18n import _ 35 from sat.core.i18n import _
26 from sat.core import exceptions 36 from sat.core import exceptions
27 from sat.tools import config 37 from sat.tools import config
28 from sat.tools.common import date_utils 38 from sat.tools.common import date_utils
29 from sat.core.log import getLogger 39 from sat.core.log import getLogger
30 from xml.sax.saxutils import quoteattr
31 import time
32 import re
33 from babel import support
34 from babel import Locale
35 from babel.core import UnknownLocaleError
36 import pygments
37 from pygments import lexers
38 from pygments import formatters
39 40
40 log = getLogger(__name__) 41 log = getLogger(__name__)
41 42
42 try: 43 try:
43 import sat_templates 44 import sat_templates
55 raise exceptions.MissingModule( 56 raise exceptions.MissingModule(
56 "Missing module jinja2, please install it from http://jinja.pocoo.org or with " 57 "Missing module jinja2, please install it from http://jinja.pocoo.org or with "
57 "pip install jinja2" 58 "pip install jinja2"
58 ) 59 )
59 60
61 from lxml import etree
60 from jinja2 import Markup as safe 62 from jinja2 import Markup as safe
61 from jinja2 import is_undefined 63 from jinja2 import is_undefined
62 from jinja2 import utils 64 from jinja2 import utils
63 from jinja2 import TemplateNotFound 65 from jinja2 import TemplateNotFound
64 from jinja2 import contextfilter 66 from jinja2 import contextfilter
65 from jinja2.loaders import split_template_path 67 from jinja2.loaders import split_template_path
66 from lxml import etree
67 68
68 HTML_EXT = ("html", "xhtml") 69 HTML_EXT = ("html", "xhtml")
69 RE_ATTR_ESCAPE = re.compile(r"[^a-z_-]") 70 RE_ATTR_ESCAPE = re.compile(r"[^a-z_-]")
70 SITE_RESERVED_NAMES = ("sat",) 71 SITE_RESERVED_NAMES = ("sat",)
71 TPL_RESERVED_CHARS = r"()/." 72 TPL_RESERVED_CHARS = r"()/."
72 RE_TPL_RESERVED_CHARS = re.compile("[" + TPL_RESERVED_CHARS + "]") 73 RE_TPL_RESERVED_CHARS = re.compile("[" + TPL_RESERVED_CHARS + "]")
74 BROWSER_DIR = "_browser"
75 BROWSER_META_FILE = "browser_meta.json"
73 76
74 TemplateData = namedtuple("TemplateData", ['site', 'theme', 'path']) 77 TemplateData = namedtuple("TemplateData", ['site', 'theme', 'path'])
75 78
76 79
77 class TemplateLoader(jinja2.BaseLoader): 80 class TemplateLoader(jinja2.BaseLoader):
348 self.host = host 351 self.host = host
349 self.trusted = trusted 352 self.trusted = trusted
350 self.sites_paths = { 353 self.sites_paths = {
351 "": os.path.dirname(sat_templates.__file__), 354 "": os.path.dirname(sat_templates.__file__),
352 } 355 }
356 self.sites_themes = {
357 }
353 conf = config.parseMainConf() 358 conf = config.parseMainConf()
354 public_sites = config.getConfig(conf, None, "sites_path_public_dict", {}) 359 public_sites = config.getConfig(conf, None, "sites_path_public_dict", {})
355 sites_data = [public_sites] 360 sites_data = [public_sites]
356 if private: 361 if private:
357 private_sites = config.getConfig(conf, None, "sites_path_private_dict", {}) 362 private_sites = config.getConfig(conf, None, "sites_path_private_dict", {})
369 log.warning(_("Can't add \"{name}\" site, it should map to an " 374 log.warning(_("Can't add \"{name}\" site, it should map to an "
370 "absolute path").format(name=name)) 375 "absolute path").format(name=name))
371 continue 376 continue
372 normalised[name] = path 377 normalised[name] = path
373 self.sites_paths.update(normalised) 378 self.sites_paths.update(normalised)
379
380 for site, site_path in self.sites_paths.items():
381 tpl_path = Path(site_path) / C.TEMPLATE_TPL_DIR
382 for p in tpl_path.iterdir():
383 if not p.is_dir():
384 continue
385 log.debug(f"theme found for {site or 'default site'}: {p.name}")
386 theme_data = self.sites_themes.setdefault(site, {})[p.name] = {'path': p}
387 browser_path = p / BROWSER_DIR
388 if browser_path.is_dir():
389 theme_data['browser_path'] = browser_path
390 browser_meta_path = browser_path / BROWSER_META_FILE
391 if browser_meta_path.is_file():
392 try:
393 with browser_meta_path.open() as f:
394 theme_data['browser_meta'] = json.load(f)
395 except Exception as e:
396 log.error(
397 f"Can't parse browser metadata at {browser_meta_path}: {e}"
398 )
399 continue
374 400
375 self.env = Environment( 401 self.env = Environment(
376 loader=TemplateLoader(sites_paths=self.sites_paths, trusted=trusted), 402 loader=TemplateLoader(sites_paths=self.sites_paths, trusted=trusted),
377 autoescape=jinja2.select_autoescape(["html", "xhtml", "xml"]), 403 autoescape=jinja2.select_autoescape(["html", "xhtml", "xml"]),
378 trim_blocks=True, 404 trim_blocks=True,
514 site_root_dir = self.sites_paths[site] 540 site_root_dir = self.sites_paths[site]
515 except KeyError: 541 except KeyError:
516 raise exceptions.NotFound 542 raise exceptions.NotFound
517 return theme, os.path.join(site_root_dir, C.TEMPLATE_TPL_DIR, theme) 543 return theme, os.path.join(site_root_dir, C.TEMPLATE_TPL_DIR, theme)
518 544
545 def getThemesData(self, site_name):
546 try:
547 return self.sites_themes[site_name]
548 except KeyError:
549 raise exceptions.NotFound(f"no theme found for {site_name}")
550
519 def getStaticPath(self, template_data, filename): 551 def getStaticPath(self, template_data, filename):
520 """Retrieve path of a static file if it exists with current theme or default 552 """Retrieve path of a static file if it exists with current theme or default
521 553
522 File will be looked at <site_root_dir>/<theme_dir>/<static_dir>/filename, 554 File will be looked at <site_root_dir>/<theme_dir>/<static_dir>/filename,
523 then <site_root_dir>/<default_theme_dir>/<static_dir>/filename anf finally 555 then <site_root_dir>/<default_theme_dir>/<static_dir>/filename anf finally