#!/usr/bin/env python3
# Copyright (C) 2016-2017 Florian Festi
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
from __future__ import annotations
import argparse
import gettext
import glob
import html
import io
import mimetypes
import os.path
import re
import sys
import threading
import time
import traceback
from typing import Any, NoReturn
from urllib.parse import quote, unquote_plus
from wsgiref.simple_server import make_server
import markdown
import qrcode
try:
import boxes.generators
except ImportError:
sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
import boxes.generators
class FileChecker(threading.Thread):
def __init__(self, files=[], checkmodules: bool = True) -> None:
super().__init__()
self.checkmodules = checkmodules
self.timestamps = {}
self._stopped = False
for path in files:
self.timestamps[path] = os.stat(path).st_mtime
if checkmodules:
self._addModules()
def _addModules(self) -> None:
for name, module in sys.modules.items():
path = getattr(module, "__file__", None)
if not path:
continue
if path not in self.timestamps:
self.timestamps[path] = os.stat(path).st_mtime
def filesOK(self) -> bool:
if self.checkmodules:
self._addModules()
for path, timestamp in self.timestamps.items():
try:
if os.stat(path).st_mtime != timestamp:
return False
except FileNotFoundError:
return False
return True
def run(self) -> None:
while not self._stopped:
if not self.filesOK():
os.execv(__file__, sys.argv)
time.sleep(1)
def stop(self) -> None:
self._stopped = True
def filter_url(url, non_default_args):
if len(url) == 0:
return ''
try:
base, args = url.split('?')
except ValueError:
return ''
args = args.split('&')
new_args = []
args_to_ignore = ["qr_code", "format"]
for arg in args:
a, b = arg.split('=')
if a.strip() in args_to_ignore:
continue
if a in non_default_args:
new_args.append(arg)
if len(new_args):
return f"{base}?{'&'.join(new_args)}"
else:
return f"{base}"
class ArgumentParserError(Exception): pass
class ThrowingArgumentParser(argparse.ArgumentParser):
def error(self, message) -> NoReturn:
raise ArgumentParserError(message)
# Evil hack
boxes.ArgumentParser = ThrowingArgumentParser # type: ignore
class BServer:
lang_re = re.compile(r"([a-z]{2,3}(-[-a-zA-Z0-9]*)?)\s*(;\s*q=(\d\.?\d*))?")
def __init__(self, url_prefix="", static_url="static") -> None:
self.boxes = {b.__name__: b for b in boxes.generators.getAllBoxGenerators().values() if b.webinterface}
self.groups = boxes.generators.ui_groups
self.groups_by_name = boxes.generators.ui_groups_by_name
for name, box in self.boxes.items():
box.UI = "web"
self.groups_by_name.get(box.ui_group,
self.groups_by_name["Misc"]).add(box)
self.staticdir = os.path.join(os.path.dirname(__file__), '../static/')
self._languages = None
self._cache: dict[Any, Any] = {}
self.url_prefix = url_prefix
self.static_url = static_url
def getLanguages(self, domain=None, localedir=None):
if self._languages is not None:
return self._languages
self._languages = []
domain = "boxes.py"
for localedir in ["locale", gettext._default_localedir]:
files = glob.glob(os.path.join(localedir, '*', 'LC_MESSAGES', '%s.mo' % domain))
self._languages.extend([file.split(os.path.sep)[-3] for file in files])
self._languages.sort()
return self._languages
def getLanguage(self, args, accept_language):
lang = None
langs = []
for i, arg in enumerate(args):
if arg.startswith("language="):
lang = arg[len("language="):]
del args[i]
break
if lang:
try:
return gettext.translation('boxes.py', localedir='locale', languages=[lang])
except OSError:
pass
try:
return gettext.translation('boxes.py', languages=[lang])
except OSError:
pass
# selected language not found try browser default
languages = accept_language.split(",")
for l in languages:
m = self.lang_re.match(l.strip())
if m:
langs.append((float(m.group(4) or 1.0), m.group(1)))
langs.sort(reverse=True)
langs = [l[1].replace("-", "_") for l in langs]
try:
return gettext.translation('boxes.py', localedir='locale', languages=langs)
except OSError:
return gettext.translation('boxes.py', languages=langs, fallback=True)
def arg2html(self, a, prefix, defaults={}, _=lambda s: s):
name = a.option_strings[0].replace("-", "")
if isinstance(a, argparse._HelpAction):
return ""
viewname = name
if prefix and name.startswith(prefix + '_'):
viewname = name[len(prefix) + 1:]
default = defaults.get(name, None)
row = """
%%s
%s
\n""" % \
(name + "_id", name, _(viewname), name + "_description", "" if not a.help else markdown.markdown(_(a.help)))
if (isinstance(a, argparse._StoreAction) and
hasattr(a.type, "html")):
input = a.type.html(name, default or a.default, _)
elif a.type == str and "\n" in a.default:
val = (default or a.default).split("\n")
input = """""" % \
(name, name, name + "_id", name + "_description", max(len(l) for l in val) + 10, len(val) + 1, default or a.default)
elif a.choices:
options = "\n".join(
"""""" %
(e, ' selected="selected"' if (e == (default or a.default)) or (str(e) == str(default or a.default)) else "",
_(e)) for e in a.choices)
input = """\n""".format(name, name, name + "_id", name + "_description", options)
else:
input = """""" % \
(name, name, name + "_id", name + "_description", default or a.default)
return row % input
def args2html_cached(self, name, box, lang, action="", defaults={}):
if defaults == {}:
key = (name, lang.info().get('language', None), action)
if key not in self._cache:
self._cache[key] = list(self.args2html(name, box, lang, action, defaults))
return self._cache[key]
return self.args2html(name, box, lang, action, defaults)
def args2html(self, name, box, lang, action="", defaults={}):
_ = lang.gettext
lang_name = lang.info().get('language', None)
langparam = ""
if lang_name:
langparam = "?language=" + lang_name
result = [f"""{self.genHTMLStart(lang)}
{_("%s - Boxes") % _(name)}
{self.genHTMLMeta()}
{self.genHTMLMetaLanguageLink()}
{self.genHTMLCSS()}
{self.genHTMLJS()}
""")
no_img_msg = _('There is no image yet. Please donate an image of your project on GitHub!')
if box.description:
result.append(
markdown.markdown(_(box.description), extensions=["extra"])
.replace('src="static/', f'src="{self.static_url}/'))
result.append(f'''