.oO SearXNG Developer Documentation Oo.
Loading...
Searching...
No Matches
__init__.py
Go to the documentation of this file.
1# SPDX-License-Identifier: AGPL-3.0-or-later
2"""Render SearXNG instance documentation.
3
4Usage in a Flask app route:
5
6.. code:: python
7
8 from searx import infopage
9 from searx.extended_types import sxng_request
10
11 _INFO_PAGES = infopage.InfoPageSet(infopage.MistletoePage)
12
13 @app.route('/info/<pagename>', methods=['GET'])
14 def info(pagename):
15
16 locale = sxng_request.preferences.get_value('locale')
17 page = _INFO_PAGES.get_page(pagename, locale)
18
19"""
20
21from __future__ import annotations
22
23__all__ = ['InfoPage', 'InfoPageSet']
24
25import os
26import os.path
27import logging
28import typing
29
30import urllib.parse
31from functools import cached_property
32import jinja2
33from flask.helpers import url_for
34from markdown_it import MarkdownIt
35
36from .. import get_setting
37from ..version import GIT_URL
38from ..locales import LOCALE_NAMES
39
40
41logger = logging.getLogger('searx.infopage')
42_INFO_FOLDER = os.path.abspath(os.path.dirname(__file__))
43INFO_PAGES: 'InfoPageSet'
44
45
46def __getattr__(name):
47 if name == 'INFO_PAGES':
48 global INFO_PAGES # pylint: disable=global-statement
49 INFO_PAGES = InfoPageSet()
50 return INFO_PAGES
51
52 raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
53
54
56 """A page of the :py:obj:`online documentation <InfoPageSet>`."""
57
58 def __init__(self, fname):
59 self.fname = fname
60
61 @cached_property
62 def raw_content(self):
63 """Raw content of the page (without any jinja rendering)"""
64 with open(self.fname, 'r', encoding='utf-8') as f:
65 return f.read()
66
67 @cached_property
68 def content(self):
69 """Content of the page (rendered in a Jinja context)"""
70 ctx = self.get_ctx()
71 template = jinja2.Environment().from_string(self.raw_content)
72 return template.render(**ctx)
73
74 @cached_property
75 def title(self):
76 """Title of the content (without any markup)"""
77 t = ""
78 for l in self.raw_content.split('\n'):
79 if l.startswith('# '):
80 t = l.strip('# ')
81 return t
82
83 @cached_property
84 def html(self):
85 """Render Markdown (CommonMark_) to HTML by using markdown-it-py_.
86
87 .. _CommonMark: https://commonmark.org/
88 .. _markdown-it-py: https://github.com/executablebooks/markdown-it-py
89
90 """
91 return (
92 MarkdownIt("commonmark", {"typographer": True}).enable(["replacements", "smartquotes"]).render(self.content)
93 )
94
95 def get_ctx(self):
96 """Jinja context to render :py:obj:`InfoPage.content`"""
97
98 def _md_link(name, url):
99 url = url_for(url, _external=True)
100 return "[%s](%s)" % (name, url)
101
102 def _md_search(query):
103 url = '%s?q=%s' % (url_for('search', _external=True), urllib.parse.quote(query))
104 return '[%s](%s)' % (query, url)
105
106 ctx = {}
107 ctx['GIT_URL'] = GIT_URL
108 ctx['get_setting'] = get_setting
109 ctx['link'] = _md_link
110 ctx['search'] = _md_search
111
112 return ctx
113
114 def __repr__(self):
115 return f'<{self.__class__.__name__} fname={self.fname!r}>'
116
117
118class InfoPageSet: # pylint: disable=too-few-public-methods
119 """Cached rendering of the online documentation a SearXNG instance has.
120
121 :param page_class: render online documentation by :py:obj:`InfoPage` parser.
122 :type page_class: :py:obj:`InfoPage`
123
124 :param info_folder: information directory
125 :type info_folder: str
126 """
127
129 self, page_class: typing.Optional[typing.Type[InfoPage]] = None, info_folder: typing.Optional[str] = None
130 ):
131 self.page_class = page_class or InfoPage
132 self.folder: str = info_folder or _INFO_FOLDER
133 """location of the Markdown files"""
134
135 self.CACHE: typing.Dict[tuple, typing.Optional[InfoPage]] = {}
136
137 self.locale_default: str = 'en'
138 """default language"""
139
140 self.locales: typing.List[str] = [
141 locale.replace('_', '-') for locale in os.listdir(_INFO_FOLDER) if locale.replace('_', '-') in LOCALE_NAMES
142 ]
143 """list of supported languages (aka locales)"""
144
145 self.toc: typing.List[str] = [
146 'search-syntax',
147 'about',
148 'donate',
149 ]
150 """list of articles in the online documentation"""
151
152 def get_page(self, pagename: str, locale: typing.Optional[str] = None):
153 """Return ``pagename`` instance of :py:obj:`InfoPage`
154
155 :param pagename: name of the page, a value from :py:obj:`InfoPageSet.toc`
156 :type pagename: str
157
158 :param locale: language of the page, e.g. ``en``, ``zh_Hans_CN``
159 (default: :py:obj:`InfoPageSet.i18n_origin`)
160 :type locale: str
161
162 """
163 locale = locale or self.locale_default
164
165 if pagename not in self.toc:
166 return None
167 if locale not in self.locales:
168 return None
169
170 cache_key = (pagename, locale)
171
172 if cache_key in self.CACHE:
173 return self.CACHE[cache_key]
174
175 # not yet instantiated
176
177 fname = os.path.join(self.folder, locale.replace('-', '_'), pagename) + '.md'
178 if not os.path.exists(fname):
179 logger.info('file %s does not exists', fname)
180 self.CACHE[cache_key] = None
181 return None
182
183 page = self.page_class(fname)
184 self.CACHE[cache_key] = page
185 return page
186
187 def iter_pages(self, locale: typing.Optional[str] = None, fallback_to_default=False):
188 """Iterate over all pages of the TOC"""
189 locale = locale or self.locale_default
190 for page_name in self.toc:
191 page_locale = locale
192 page = self.get_page(page_name, locale)
193 if fallback_to_default and page is None:
194 page_locale = self.locale_default
195 page = self.get_page(page_name, self.locale_default)
196 if page is not None:
197 # page is None if the page was deleted by the administrator
198 yield page_name, page_locale, page
iter_pages(self, typing.Optional[str] locale=None, fallback_to_default=False)
Definition __init__.py:187
get_page(self, str pagename, typing.Optional[str] locale=None)
Definition __init__.py:152
__init__(self, typing.Optional[typing.Type[InfoPage]] page_class=None, typing.Optional[str] info_folder=None)
Definition __init__.py:130
__init__(self, fname)
Definition __init__.py:58
__getattr__(name)
Definition __init__.py:46