[help] substitute variables with string.Template

Previously help pages could link to instance-specific pages
with e.g. [search engines][url_for:preferences] which was
possible because searx.user_help was prefixing the Markdown:

[url_for:preferences]: /preferences

There are two problems with this:

1. typos in the Markdown files could go by unnoticed
   (since Markdown treats [foo][doesntexist] as regular text)

2. it doesn't let Markdown append their own query string
   (which would however be handy to link example search
    queries to the search page)

This commit addresses both of these problems by using
string.Template from the Python standard library.

Why don't we use Jinja2 for this? Well Jinja2 would be overkill.
We don't need any logic in the user documentation. And in the future
we might outsource the user documentation translation to Weblate,
in which case not permitting any logic in them is more secure.
This commit is contained in:
Martin Fischer 2022-01-31 13:46:16 +01:00
parent 49d7aa323d
commit 244ffef315
2 changed files with 27 additions and 15 deletions

View file

@ -1,12 +1,12 @@
# About SearXNG
SearXNG is a fork from the well-known [searx] [metasearch engine], aggregating
the results of other [search engines][url_for:preferences] while not storing
the results of other [search engines]($preferences) while not storing
information about its users.
More about SearXNG ...
* [SearXNG sources][brand.git_url]
* [SearXNG sources]($brand.git_url)
* [weblate]
---
@ -20,7 +20,7 @@ More about SearXNG ...
with a third party, and it can't be used to compromise you.
* SearXNG is free software, the code is 100% open and you can help
to make it better. See more on [SearXNG sources][brand.git_url].
to make it better. See more on [SearXNG sources]($brand.git_url).
If you do care about privacy, want to be a conscious user, or otherwise
believe in digital freedom, make SearXNG your default search engine or run
@ -49,20 +49,20 @@ search engine, see your browser's documentation:
## Where to find anonymous usage statistics of this instance ?
[Stats page][url_for:stats] contains some useful data about the engines used.
[Stats page]($stats) contains some useful data about the engines used.
## How can I make it my own?
SearXNG appreciates your concern regarding logs, so take the code from
the [SearXNG project][brand.git_url] and run it yourself!
the [SearXNG project]($brand.git_url) and run it yourself!
Add your instance to this [list of public instances][brand.public_instances] to
Add your instance to this [list of public instances]($brand.public_instances) to
help other people reclaim their privacy and make the Internet freer! The more
decentralized the Internet is, the more freedom we have!
## Where are the docs & code of this instance?
See the [SearXNG docs][brand.docs_url] and [SearXNG sources][brand.git_url]
See the [SearXNG docs]($brand.docs_url) and [SearXNG sources]($brand.git_url)
[searx]: https://github.com/searx/searx
[metasearch engine]: https://en.wikipedia.org/wiki/Metasearch_engine

View file

@ -1,6 +1,8 @@
# pyright: basic
from typing import Dict, NamedTuple
import pkg_resources
import string
import sys
import flask
from flask.helpers import url_for
@ -22,6 +24,10 @@ PAGES: Dict[str, HelpPage] = {}
""" Maps a filename under help/ without the file extension to the rendered page. """
class Template(string.Template):
idpattern = '(:?[a-z._:]+)'
def render(app: flask.Flask):
"""
Renders the user documentation. Must be called after all Flask routes have been
@ -30,7 +36,7 @@ def render(app: flask.Flask):
We render the user documentation once on startup to improve performance.
"""
link_targets = {
variables = {
'brand.git_url': GIT_URL,
'brand.public_instances': get_setting('brand.public_instances'),
'brand.docs_url': get_setting('brand.docs_url'),
@ -40,15 +46,21 @@ def render(app: flask.Flask):
# we specify base_url so that url_for works for base_urls that have a non-root path
with app.test_request_context(base_url=base_url):
link_targets['url_for:index'] = url_for('index')
link_targets['url_for:preferences'] = url_for('preferences')
link_targets['url_for:stats'] = url_for('stats')
define_link_targets = ''.join(f'[{name}]: {url}\n' for name, url in link_targets.items())
variables['index'] = url_for('index')
variables['preferences'] = url_for('preferences')
variables['stats'] = url_for('stats')
for pagename in _TOC:
file_content = pkg_resources.resource_string(__name__, 'help/en/' + pagename + '.md').decode()
markdown = define_link_targets + file_content
filename = 'help/en/' + pagename + '.md'
file_content = pkg_resources.resource_string(__name__, filename).decode()
try:
markdown = Template(file_content).substitute(variables)
except KeyError as e:
print('[FATAL ERROR] undefined variable ${} in {}'.format(e.args[0], filename))
print('available variables are:')
for key in variables:
print('\t' + key)
sys.exit(1)
assert file_content.startswith('# ')
title = file_content.split('\n', maxsplit=1)[0].strip('# ')
content: str = mistletoe.markdown(markdown)