searxng/searx/templates.py
2021-08-14 18:12:09 +02:00

192 lines
6.2 KiB
Python

import os
from typing import Optional
from urllib.parse import parse_qs, urlencode, urlsplit
import jinja2
import babel.support
from pygments import highlight
from pygments.lexers import get_lexer_by_name
from pygments.formatters import HtmlFormatter # pylint: disable=no-name-in-module
from starlette.requests import Request
from starlette.templating import Jinja2Templates
from starlette_context import context
from starlette.routing import NoMatchFound
from starlette_i18n import i18n
from searx import logger, settings
from searx.webutils import (
get_static_files,
get_result_templates,
get_themes,
)
# about static
logger.debug('static directory is %s', settings['ui']['static_path'])
static_files = get_static_files(settings['ui']['static_path'])
# about templates
logger.debug('templates directory is %s', settings['ui']['templates_path'])
default_theme = settings['ui']['default_theme']
templates_path = settings['ui']['templates_path']
themes = get_themes(templates_path)
result_templates = get_result_templates(templates_path)
global_favicons = []
for indice, theme in enumerate(themes):
global_favicons.append([])
theme_img_path = os.path.join(settings['ui']['static_path'], 'themes', theme, 'img', 'icons')
for (dirpath, dirnames, filenames) in os.walk(theme_img_path):
global_favicons[indice].extend(filenames)
def get_current_theme_name(request: Request, override: Optional[str] =None) -> str:
"""Returns theme name.
Checks in this order:
1. override
2. cookies
3. settings"""
if override and (override in themes or override == '__common__'):
return override
theme_name = request.query_params.get('theme', context.preferences.get_value('theme')) # pylint: disable=no-member
if theme_name not in themes:
theme_name = default_theme
return theme_name
def get_result_template(theme_name: str, template_name: str) -> str:
themed_path = theme_name + '/result_templates/' + template_name
if themed_path in result_templates:
return themed_path
return 'result_templates/' + template_name
# code-highlighter
def code_highlighter(codelines, language=None):
if not language:
language = 'text'
try:
# find lexer by programing language
lexer = get_lexer_by_name(language, stripall=True)
except Exception as e: # pylint: disable=broad-except
logger.exception(e, exc_info=True)
# if lexer is not found, using default one
lexer = get_lexer_by_name('text', stripall=True)
html_code = ''
tmp_code = ''
last_line = None
# parse lines
for line, code in codelines:
if not last_line:
line_code_start = line
# new codeblock is detected
if last_line is not None and\
last_line + 1 != line:
# highlight last codepart
formatter = HtmlFormatter(
linenos='inline', linenostart=line_code_start, cssclass="code-highlight"
)
html_code = html_code + highlight(tmp_code, lexer, formatter)
# reset conditions for next codepart
tmp_code = ''
line_code_start = line
# add codepart
tmp_code += code + '\n'
# update line
last_line = line
# highlight last codepart
formatter = HtmlFormatter(linenos='inline', linenostart=line_code_start, cssclass="code-highlight")
html_code = html_code + highlight(tmp_code, lexer, formatter)
return html_code
class I18NTemplates(Jinja2Templates):
"""Custom Jinja2Templates with i18n support
"""
@staticmethod
def url_for_theme(endpoint: str, override_theme=None, **values):
request = context.request # pylint: disable=no-member
# starlette migration
if '_external' in values:
del values['_external']
if 'filename' in values:
values['path'] = values['filename']
del values['filename']
#
if endpoint == 'static' and values.get('path'):
theme_name = get_current_theme_name(request, override=override_theme)
filename_with_theme = "themes/{}/{}".format(theme_name, values['path'])
if filename_with_theme in static_files:
values['path'] = filename_with_theme
return request.url_for(endpoint, **values)
try:
url_for_args = {}
for k in ('path', 'filename'):
if k in values:
v = values.pop(k)
url_for_args[k] = v
url = request.url_for(endpoint, **url_for_args)
_url = urlsplit(url)
_query = parse_qs(_url.query)
_query.update(values)
querystr = urlencode(_query, doseq=True)
return _url._replace(query=querystr).geturl()
# if anchor is not None:
# rv += f"#{url_quote(anchor)}"
except NoMatchFound as e:
error_message = "url_for, endpoint='%s' not found (values=%s)" % (endpoint, str(values))
logger.error(error_message)
context.errors.append(error_message) # pylint: disable=no-member
raise e
@staticmethod
def ugettext(message):
translations = i18n.get_locale().translations
if isinstance(message, babel.support.LazyProxy):
message = message.value
return translations.ugettext(message)
@staticmethod
def ungettext(*args):
translations = i18n.get_locale().translations
return translations.ungettext(*args)
def _create_env(self, directory: str) -> "jinja2.Environment":
loader = jinja2.FileSystemLoader(directory)
env = jinja2.Environment(
loader=loader,
autoescape=True,
trim_blocks=True,
lstrip_blocks=True,
auto_reload=False,
extensions=[
'jinja2.ext.loopcontrols',
'jinja2.ext.i18n'
],
)
env.filters["code_highlighter"] = code_highlighter
env.globals["url_for"] = I18NTemplates.url_for_theme
env.install_gettext_callables( # pylint: disable=no-member
I18NTemplates.ugettext,
I18NTemplates.ungettext,
newstyle=True
)
return env