mightyscape-1.2/extensions/fablabchemnitz/boxes.py/boxes/scripts/boxesserver

712 lines
25 KiB
Plaintext
Raw Normal View History

2022-11-30 15:57:58 +01:00
#!/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 <http://www.gnu.org/licenses/>.
2024-03-31 18:51:28 +02:00
from __future__ import annotations
2022-11-30 15:57:58 +01:00
import argparse
2024-03-31 18:51:28 +02:00
import gettext
import glob
2022-11-30 15:57:58 +01:00
import html
2024-03-31 18:51:28 +02:00
import io
import mimetypes
2022-11-30 15:57:58 +01:00
import os.path
2024-03-31 18:51:28 +02:00
import re
import sys
2022-11-30 15:57:58 +01:00
import threading
import time
import traceback
2024-03-31 18:51:28 +02:00
from typing import Any, NoReturn
from urllib.parse import quote, unquote_plus
2022-11-30 15:57:58 +01:00
from wsgiref.simple_server import make_server
2024-03-31 18:51:28 +02:00
import markdown
import qrcode
2022-11-30 15:57:58 +01:00
try:
import boxes.generators
except ImportError:
sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
import boxes.generators
2024-03-31 18:51:28 +02:00
2022-11-30 15:57:58 +01:00
class FileChecker(threading.Thread):
2024-03-31 18:51:28 +02:00
def __init__(self, files=[], checkmodules: bool = True) -> None:
super().__init__()
2022-11-30 15:57:58 +01:00
self.checkmodules = checkmodules
self.timestamps = {}
self._stopped = False
for path in files:
self.timestamps[path] = os.stat(path).st_mtime
if checkmodules:
self._addModules()
2024-03-31 18:51:28 +02:00
def _addModules(self) -> None:
2022-11-30 15:57:58 +01:00
for name, module in sys.modules.items():
path = getattr(module, "__file__", None)
if not path:
continue
if path not in self.timestamps:
2024-03-31 18:51:28 +02:00
self.timestamps[path] = os.stat(path).st_mtime
2022-11-30 15:57:58 +01:00
2024-03-31 18:51:28 +02:00
def filesOK(self) -> bool:
2022-11-30 15:57:58 +01:00
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
2024-03-31 18:51:28 +02:00
def run(self) -> None:
2022-11-30 15:57:58 +01:00
while not self._stopped:
if not self.filesOK():
os.execv(__file__, sys.argv)
time.sleep(1)
2024-03-31 18:51:28 +02:00
def stop(self) -> None:
2022-11-30 15:57:58 +01:00
self._stopped = True
2024-03-31 18:51:28 +02:00
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}"
2022-11-30 15:57:58 +01:00
class ArgumentParserError(Exception): pass
2024-03-31 18:51:28 +02:00
2022-11-30 15:57:58 +01:00
class ThrowingArgumentParser(argparse.ArgumentParser):
2024-03-31 18:51:28 +02:00
def error(self, message) -> NoReturn:
2022-11-30 15:57:58 +01:00
raise ArgumentParserError(message)
2024-03-31 18:51:28 +02:00
# Evil hack
boxes.ArgumentParser = ThrowingArgumentParser # type: ignore
class BServer:
2022-11-30 15:57:58 +01:00
lang_re = re.compile(r"([a-z]{2,3}(-[-a-zA-Z0-9]*)?)\s*(;\s*q=(\d\.?\d*))?")
2024-03-31 18:51:28 +02:00
def __init__(self, url_prefix="", static_url="static") -> None:
self.boxes = {b.__name__: b for b in boxes.generators.getAllBoxGenerators().values() if b.webinterface}
2022-11-30 15:57:58 +01:00
self.groups = boxes.generators.ui_groups
self.groups_by_name = boxes.generators.ui_groups_by_name
for name, box in self.boxes.items():
2024-03-31 18:51:28 +02:00
box.UI = "web"
2022-11-30 15:57:58 +01:00
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
2024-03-31 18:51:28 +02:00
self._cache: dict[Any, Any] = {}
self.url_prefix = url_prefix
self.static_url = static_url
2022-11-30 15:57:58 +01:00
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:
2024-03-31 18:51:28 +02:00
return gettext.translation('boxes.py', localedir='locale', languages=[lang])
2022-11-30 15:57:58 +01:00
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:
2024-03-31 18:51:28 +02:00
return gettext.translation('boxes.py', localedir='locale', languages=langs)
2022-11-30 15:57:58 +01:00
except OSError:
return gettext.translation('boxes.py', languages=langs, fallback=True)
2024-03-31 18:51:28 +02:00
def arg2html(self, a, prefix, defaults={}, _=lambda s: s):
2022-11-30 15:57:58 +01:00
name = a.option_strings[0].replace("-", "")
if isinstance(a, argparse._HelpAction):
return ""
viewname = name
if prefix and name.startswith(prefix + '_'):
2024-03-31 18:51:28 +02:00
viewname = name[len(prefix) + 1:]
2022-11-30 15:57:58 +01:00
default = defaults.get(name, None)
row = """<tr><td id="%s"><label for="%s">%s</label></td><td>%%s</td><td id="%s">%s</td></tr>\n""" % \
2024-03-31 18:51:28 +02:00
(name + "_id", name, _(viewname), name + "_description", "" if not a.help else markdown.markdown(_(a.help)))
2022-11-30 15:57:58 +01:00
if (isinstance(a, argparse._StoreAction) and
2024-03-31 18:51:28 +02:00
hasattr(a.type, "html")):
2022-11-30 15:57:58 +01:00
input = a.type.html(name, default or a.default, _)
2024-03-31 18:51:28 +02:00
elif a.type == str and "\n" in a.default:
2022-11-30 15:57:58 +01:00
val = (default or a.default).split("\n")
input = """<textarea name="%s" id="%s" aria-labeledby="%s %s" cols="%s" rows="%s">%s</textarea>""" % \
2024-03-31 18:51:28 +02:00
(name, name, name + "_id", name + "_description", max(len(l) for l in val) + 10, len(val) + 1, default or a.default)
2022-11-30 15:57:58 +01:00
elif a.choices:
options = "\n".join(
2024-03-31 18:51:28 +02:00
"""<option value="%s"%s>%s</option>""" %
(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 = """<select name="{}" id="{}" aria-labeledby="{} {}" size="1">\n{}</select>\n""".format(name, name, name + "_id", name + "_description", options)
2022-11-30 15:57:58 +01:00
else:
2024-03-31 18:51:28 +02:00
input = """<input name="%s" id="%s" aria-labeledby="%s %s" type="text" value="%s">""" % \
(name, name, name + "_id", name + "_description", default or a.default)
2022-11-30 15:57:58 +01:00
return row % input
2024-03-31 18:51:28 +02:00
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)
2022-11-30 15:57:58 +01:00
def args2html(self, name, box, lang, action="", defaults={}):
_ = lang.gettext
lang_name = lang.info().get('language', None)
2024-03-31 18:51:28 +02:00
langparam = ""
2022-11-30 15:57:58 +01:00
if lang_name:
langparam = "?language=" + lang_name
2024-03-31 18:51:28 +02:00
result = [f"""{self.genHTMLStart(lang)}
2022-11-30 15:57:58 +01:00
<head>
2024-03-31 18:51:28 +02:00
<title>{_("%s - Boxes") % _(name)}</title>
{self.genHTMLMeta()}
{self.genHTMLMetaLanguageLink()}
{self.genHTMLCSS()}
{self.genHTMLJS()}
2022-11-30 15:57:58 +01:00
</head>
2024-03-31 18:51:28 +02:00
<body onload="initPage({len(box.argparser._action_groups) - 3})">
2022-11-30 15:57:58 +01:00
2024-03-31 18:51:28 +02:00
<div class="container">
2022-11-30 15:57:58 +01:00
<div style="float: left;">
2024-03-31 18:51:28 +02:00
<a href="./{langparam}"><h1>{_("Boxes.py")}</h1></a>
2022-11-30 15:57:58 +01:00
</div>
<div style="width: 120px; float: right;">
2024-03-31 18:51:28 +02:00
<img alt="self-Logo" src="{self.static_url}/boxes-logo.svg" width="120">
2022-11-30 15:57:58 +01:00
</div>
<div>
<div class="clear"></div>
<hr>
2024-03-31 18:51:28 +02:00
<h2 style="margin: 0px 0px 0px 20px;">{_(name)}</h2>
<p>{_(box.__doc__) if box.__doc__ else ""}</p>
<form action="{action}" method="GET" rel="nofollow">
"""]
2022-11-30 15:57:58 +01:00
groupid = 0
for group in box.argparser._action_groups[3:] + box.argparser._action_groups[:3]:
if not group._group_actions:
continue
if len(group._group_actions) == 1 and isinstance(group._group_actions[0], argparse._HelpAction):
continue
prefix = getattr(group, "prefix", None)
2024-03-31 18:51:28 +02:00
result.append(f'''<h3 id="h-{groupid}" data-id="{groupid}" role="button" aria-expanded="true" tabindex="0" class="toggle open">{_(group.title)}</h3>\n<table role="presentation" id="{groupid}">\n''')
2022-11-30 15:57:58 +01:00
for a in group._group_actions:
if a.dest in ("input", "output"):
continue
result.append(self.arg2html(a, prefix, defaults, _))
result.append("</table>")
groupid += 1
2024-03-31 18:51:28 +02:00
result.append(f"""
<input type="hidden" name="language" id="language" value="{lang_name}">
2022-11-30 15:57:58 +01:00
<p>
2024-03-31 18:51:28 +02:00
<button name="render" value="1" formtarget="_blank">{_("Generate")}</button>
<button name="render" value="2" formtarget="_self">{_("Download")}</button>
<button name="render" value="0" formtarget="_self">{_("Save to URL")}</button>
<button name="render" value="3" formtarget="_blank">{_("QR Code")}</button>
2022-11-30 15:57:58 +01:00
</p>
</form>
</div>
2024-03-31 18:51:28 +02:00
2022-11-30 15:57:58 +01:00
<div class="clear"></div>
<hr>
<div class="description">
""")
2024-03-31 18:51:28 +02:00
no_img_msg = _('There is no image yet. Please donate an image of your project on <a href=&quot;https://github.com/florianfesti/boxes/issues/628&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;>GitHub</a>!')
2022-11-30 15:57:58 +01:00
if box.description:
2024-03-31 18:51:28 +02:00
result.append(
markdown.markdown(_(box.description), extensions=["extra"])
.replace('src="static/', f'src="{self.static_url}/'))
2022-11-30 15:57:58 +01:00
result.append(f'''<div>
2024-03-31 18:51:28 +02:00
<img style="width:100%;" src="{self.static_url}/samples/{box.__class__.__name__}.jpg" onerror="this.parentElement.innerHTML = '{no_img_msg}';" alt="Picture of box.">
2022-11-30 15:57:58 +01:00
</div>
</div>
</div>
2024-03-31 18:51:28 +02:00
{self.genPagePartFooter(lang)}
</body>
2022-11-30 15:57:58 +01:00
</html>
2024-03-31 18:51:28 +02:00
''')
2022-11-30 15:57:58 +01:00
return (s.encode("utf-8") for s in result)
2024-03-31 18:51:28 +02:00
def genPageMenu(self, lang):
2022-11-30 15:57:58 +01:00
_ = lang.gettext
lang_name = lang.info().get('language', None)
2024-03-31 18:51:28 +02:00
langparam = ""
2022-11-30 15:57:58 +01:00
if lang_name:
langparam = "?language=" + lang_name
2024-03-31 18:51:28 +02:00
result = [f"""{self.genHTMLStart(lang)}
2022-11-30 15:57:58 +01:00
<head>
2024-03-31 18:51:28 +02:00
<title>{_("Boxes.py")}</title>
{self.genHTMLMeta()}
{self.genHTMLMetaLanguageLink()}
{self.genHTMLCSS()}
{self.genHTMLJS()}
2022-11-30 15:57:58 +01:00
</head>
2024-03-31 18:51:28 +02:00
<body onload="initPage()">
<div class="container">
2022-11-30 15:57:58 +01:00
<div style="width: 75%; float: left;">
2024-03-31 18:51:28 +02:00
{self.genPagePartHeader(lang)}
2022-11-30 15:57:58 +01:00
2024-03-31 18:51:28 +02:00
<div class="modenav">
<span class="modebutton"><a href="Gallery">{_("Gallery")}</a></span>
<span class="modebutton modeactive">{_("Menu")}</span>
2022-11-30 15:57:58 +01:00
</div>
2024-03-31 18:51:28 +02:00
<br>
2022-11-30 15:57:58 +01:00
<div class="menu" style="width: 100%">
2024-03-31 18:51:28 +02:00
<img style="width: 200px;" id="sample-preview" src="{self.static_url}/nothing.png" alt="">
"""]
2022-11-30 15:57:58 +01:00
for nr, group in enumerate(self.groups):
result.append(f'''
2024-03-31 18:51:28 +02:00
<h3 id="h-{nr}"
data-id="{nr}"
data-thumbnail="{self.static_url}/samples/{group.thumbnail}"
role="button"
aria-expanded="false"
class="toggle thumbnail open"
tabindex="0"
>
{_(group.title)}
</h3>
<div id="{nr}">\n <ul>\n''')
2022-11-30 15:57:58 +01:00
for box in group.generators:
name = box.__name__
docs = ""
if box.__doc__:
docs = " - " + _(box.__doc__)
2024-03-31 18:51:28 +02:00
result.append(f""" <li class="thumbnail" data-thumbnail="{self.static_url}/samples/{name}-thumb.jpg" id="search_id_{name}"><a href="{name}{langparam}">{_(name)}</a>{docs}</li>\n""")
result.append(" </ul>\n </div>\n")
result.append(f"""
2022-11-30 15:57:58 +01:00
</div>
<div style="width: 5%; float: left;"></div>
<div class="clear"></div>
<hr>
</div>
2024-03-31 18:51:28 +02:00
</div>
{self.genPagePartFooter(lang)}
2022-11-30 15:57:58 +01:00
</body>
</html>
""")
return (s.encode("utf-8") for s in result)
2024-03-31 18:51:28 +02:00
def genHTMLStart(self, lang) -> str:
lang_attr = lang.info().get("language", "")
if lang_attr != "":
return f"""<!DOCTYPE html><html lang="{lang_attr.replace('_', '-')}">"""
return "<!DOCTYPE html><html>"
def genHTMLMeta(self) -> str:
return f'''
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="flattr:id" content="456799">
<link rel="icon" type="image/svg+xml" href="{self.static_url}/boxes-logo.svg" sizes="any">
<link rel="icon" type="image/x-icon" href="{self.static_url}/favicon.ico">
'''
def genHTMLMetaLanguageLink(self) -> str:
"""Generates meta language list for search engines."""
languages = self.getLanguages()
s = ""
for language in languages:
s += f'<link rel="alternate" hreflang="{language.replace("_", "-")}" href="https://www.festi.info/boxes.py/?language={language}">\n'
return s
2022-11-30 15:57:58 +01:00
2024-03-31 18:51:28 +02:00
def genHTMLCSS(self) -> str:
return f'<link rel="stylesheet" href="{self.static_url}/self.css">'
def genHTMLJS(self) -> str:
return f'<script src="{self.static_url}/self.js"></script>'
def genHTMLLanguageSelection(self, lang) -> str:
"""Generates a dropdown selection for the language change."""
current_language = lang.info().get('language', '')
languages = self.getLanguages()
if len(languages) < 2:
return "<!-- No other languages to select found. -->"
html_option = ""
for language in languages:
html_option += f"<option value='{language}'{' selected' if language == current_language else ''}>{language}</option>\n"
return """
<form>
<select name="language" onchange='if(this.value != \"""" + current_language + """\") { this.form.submit(); }'>
""" + html_option + """
</select>
</form>
"""
def genPagePartHeader(self, lang) -> str:
2022-11-30 15:57:58 +01:00
_ = lang.gettext
2024-03-31 18:51:28 +02:00
lang_name = lang.info().get('language', None)
langparam = ""
if lang_name:
langparam = "?language=" + lang_name
return f"""
<h1><a href="./{langparam}">{_("Boxes.py")}</a></h1>
<p>{_("Create boxes and more with a laser cutter!")}</p>
<p>
{_('''
<a href="https://hackaday.io/project/10649-boxespy">Boxes.py</a> is an <a href="https://www.gnu.org/licenses/gpl-3.0.en.html">Open Source</a> box generator written in <a href="https://www.python.org/">Python</a>. It features both finished parametrized generators as well as a Python API for writing your own. It features finger and (flat) dovetail joints, flex cuts, holes and slots for screws, hinges, gears, pulleys and much more.''')}
</p>
</div>
<div style="width: 25%; float: left;">
<img alt="self-Logo" src="{self.static_url}/boxes-logo.svg" width="250">
</div>
<div>
<div class="clear"></div>
<div class="search">
\U0001f50d <input autocomplete="off" type="search" oninput="filterSearchItems();" name="search" id="search" placeholder="Search">
</div>"""
def genPagePartFooter(self, lang) -> str:
_ = lang.gettext
2022-11-30 15:57:58 +01:00
return """
2024-03-31 18:51:28 +02:00
<div class="footer">
2022-11-30 15:57:58 +01:00
<ul>
2024-03-31 18:51:28 +02:00
<li>""" + self.genHTMLLanguageSelection(lang) + """</li>
<li><a href="https://florianfesti.github.io/boxes/html/usermanual.html" target="_blank" rel="noopener">""" + _("Help") + """</a></li>
<li><a href="https://hackaday.io/project/10649-boxespy" target="_blank" rel="noopener">""" + _("Home Page") + """</a></li>
<li><a href="https://florianfesti.github.io/boxes/html/index.html" target="_blank" rel="noopener">""" + _("Documentation") + """</a></li>
<li><a href="https://github.com/florianfesti/boxes" target="_blank" rel="noopener">""" + _("Sources") + """</a></li>
2022-11-30 15:57:58 +01:00
</ul>
</div>
"""
2024-03-31 18:51:28 +02:00
def genPageError(self, name, e, lang) -> list[bytes]:
"""Generates a error page."""
_ = lang.gettext
h = f"""{self.genHTMLStart(lang)}
2022-11-30 15:57:58 +01:00
<head>
2024-03-31 18:51:28 +02:00
<title>{_("Error generating %s") % _(name)}</title>
{self.genHTMLMeta()}
<meta name="robots" content="noindex">
2022-11-30 15:57:58 +01:00
</head>
<body>
2024-03-31 18:51:28 +02:00
<h1>{_("An error occurred!")}</h1>
2022-11-30 15:57:58 +01:00
"""
2024-03-31 18:51:28 +02:00
for s in str(e).split("\n"):
h += f"<p>{html.escape(s)}</p>\n"
h += "</body></html>"
return [h.encode("utf-8")]
2022-11-30 15:57:58 +01:00
def serveStatic(self, environ, start_response):
filename = environ["PATH_INFO"][len("/static/"):]
path = os.path.join(self.staticdir, filename)
if (not re.match(r"[a-zA-Z0-9_/-]+\.[a-zA-Z0-9]+", filename) or
2024-03-31 18:51:28 +02:00
not os.path.exists(path)):
2022-11-30 15:57:58 +01:00
if re.match(r"samples/.*-thumb.jpg", filename):
path = os.path.join(self.staticdir, "nothing.png")
else:
2024-03-31 18:51:28 +02:00
start_response("404 Not Found", [('Content-type', 'text/plain')])
2022-11-30 15:57:58 +01:00
return [b"Not found"]
type_, encoding = mimetypes.guess_type(filename)
if encoding is None:
encoding = "utf-8"
# Images do not have charset. Just bytes. Except text based svg.
# Todo: fallback if type_ is None?
if type_ is not None and "image" in type_ and type_ != "image/svg+xml":
start_response("200 OK", [('Content-type', "%s" % type_)])
else:
2024-03-31 18:51:28 +02:00
start_response("200 OK", [('Content-type', f"{type_}; charset={encoding}")])
2022-11-30 15:57:58 +01:00
f = open(path, 'rb')
2024-03-31 18:51:28 +02:00
return environ['wsgi.file_wrapper'](f, 512 * 1024)
2022-11-30 15:57:58 +01:00
2024-03-31 18:51:28 +02:00
def getURL(self, environ) -> str:
url = environ['wsgi.url_scheme'] + '://'
2022-11-30 15:57:58 +01:00
if environ.get('HTTP_HOST'):
url += environ['HTTP_HOST']
else:
url += environ['SERVER_NAME']
if environ['wsgi.url_scheme'] == 'https':
if environ['SERVER_PORT'] != '443':
url += ':' + environ['SERVER_PORT']
else:
if environ['SERVER_PORT'] != '80':
url += ':' + environ['SERVER_PORT']
2024-03-31 18:51:28 +02:00
url += quote(self.url_prefix)
2022-11-30 15:57:58 +01:00
url += quote(environ.get('SCRIPT_NAME', ''))
url += quote(environ.get('PATH_INFO', ''))
if environ.get('QUERY_STRING'):
url += '?' + environ['QUERY_STRING']
return url
2024-03-31 18:51:28 +02:00
def serveGallery(self, environ, start_response, lang):
_ = lang.gettext
lang_name = lang.info().get('language', None)
start_response("200 OK", [('Content-type', "text/html; charset=utf-8")])
if ("Gallery", lang_name) in self._cache:
return self._cache[("Gallery", lang_name)]
langparam = ""
if lang_name:
langparam = "?language=" + lang_name
result = [f"""
{self.genHTMLStart(lang)}
<head>
<title>{_("Gallery")} - {_("Boxes.py")}</title>
{self.genHTMLMeta()}
{self.genHTMLMetaLanguageLink()}
{self.genHTMLCSS()}
{self.genHTMLJS()}
</head>
<body onload="initPage()">
<div class="container">
<div style="width: 75%; float: left;">
{self.genPagePartHeader(lang)}
<div class="modenav">
<span class="modebutton modeactive">{_("Gallery")}</span>
<span class="modebutton"><a href="Menu">{_("Menu")}</a></span>
</div>
"""]
for nr, group in enumerate(self.groups):
result.append(f"<h2>{_(group.title)}</h2>\n")
for box in group.generators:
name = box.__name__
fn = f"samples/{name}-thumb.jpg"
thumbnail = f"{self.static_url}/{fn}"
static_filename = os.path.join(self.staticdir, fn)
alt = f"{_(name)}"
href = f"{name}{langparam}"
if not os.path.exists(static_filename):
result.append(f""" <span class="gallery_missing" id="search_id_{name}"><a href="{href}">{_(name)}<br><br>{_(box.__doc__)}</a></span>\n""")
else:
result.append(f""" <span class="gallery" id="search_id_{name}"><a title="{_(name)} - {_(box.__doc__)}" href="{href}"><img alt="{alt}" src="{thumbnail}"/></a></span>\n""")
result.append(f"""
</div><div style="width: 5%; float: left;"></div>
<div class="clear"></div><hr></div>
{self.genPagePartFooter(lang)}
</body>
</html>
"""
)
self._cache[("Gallery", lang_name)] = [s.encode("utf-8") for s in result]
return self._cache[("Gallery", lang_name)]
2022-11-30 15:57:58 +01:00
def serve(self, environ, start_response):
2024-03-31 18:51:28 +02:00
# serve favicon from static for generated SVGs
if environ["PATH_INFO"] == "favicon.ico":
environ["PATH_INFO"] = "/static/favicon.ico"
2022-11-30 15:57:58 +01:00
if environ["PATH_INFO"].startswith("/static/"):
return self.serveStatic(environ, start_response)
status = '200 OK'
headers = [('Content-type', 'text/html; charset=utf-8'), ('X-XSS-Protection', '1; mode=block'), ('X-Content-Type-Options', 'nosniff'), ('x-frame-options', 'SAMEORIGIN'), ('Referrer-Policy', 'no-referrer')]
name = environ["PATH_INFO"][1:]
2024-03-31 18:51:28 +02:00
args = [unquote_plus(arg) for arg in environ.get('QUERY_STRING', '').split("&")]
render = "0"
for arg in args:
if arg.startswith("render="):
render = arg[len("render="):]
2022-11-30 15:57:58 +01:00
lang = self.getLanguage(args, environ.get("HTTP_ACCEPT_LANGUAGE", ""))
_ = lang.gettext
2024-03-31 18:51:28 +02:00
if not name or name == "Gallery":
return self.serveGallery(environ, start_response, lang)
2022-11-30 15:57:58 +01:00
box_cls = self.boxes.get(name, None)
if not box_cls:
start_response(status, headers)
2024-03-31 18:51:28 +02:00
lang_name = lang.info().get('language', None)
if lang_name not in self._cache:
self._cache[lang_name] = list(self.genPageMenu(lang))
return self._cache[lang_name]
box = box_cls()
2022-11-30 15:57:58 +01:00
2024-03-31 18:51:28 +02:00
box.translations = lang
if render == "0":
defaults = {}
2022-11-30 15:57:58 +01:00
for a in args:
kv = a.split('=')
if len(kv) == 2:
k, v = kv
defaults[k] = html.escape(v, True)
start_response(status, headers)
2024-03-31 18:51:28 +02:00
return self.args2html_cached(name, box, lang, "./" + name, defaults=defaults)
2022-11-30 15:57:58 +01:00
2024-03-31 18:51:28 +02:00
args = ["--" + arg for arg in args if not arg.startswith("render=")]
try:
box.parseArgs(args)
except ArgumentParserError as e:
start_response(status, headers)
return self.genPageError(name, e, lang)
try:
box.metadata["url"] = self.getURL(environ)
box.metadata["url_short"] = filter_url(box.metadata["url"],
box.non_default_args)
box.open()
box.render()
data = box.close()
except Exception as e:
if not isinstance(e, ValueError):
print("Exception during rendering:")
traceback.print_exc()
start_response("500 Internal Server Error", headers)
return self.genPageError(name, e, lang)
http_headers = box.formats.http_headers.get(box.format, [('Content-type', 'application/unknown; charset=utf-8')])[:]
# Prevent crawlers.
http_headers.append(('X-Robots-Tag', 'noindex,nofollow'))
if render == "3":
http_headers = [('Content-type', 'image/png')]
http_headers.append(('X-Robots-Tag', 'noindex,nofollow'))
qr_format = "png"
fn = box.__class__.__name__
2022-11-30 15:57:58 +01:00
start_response(status, http_headers)
2024-03-31 18:51:28 +02:00
qrcode = get_qrcode(box.metadata["url_short"], qr_format)
return (qrcode,)
if box.format != "svg" or render == "2":
extension = box.format
if extension == "svg_Ponoko":
extension = "svg"
http_headers.append(('Content-Disposition', f'attachment; filename="{box.__class__.__name__}.{extension}"'))
start_response(status, http_headers)
return environ['wsgi.file_wrapper'](data, 512 * 1024)
def get_qrcode(url, format):
if url is None:
url = "no url"
img = qrcode.make(url)
image_bytes = io.BytesIO()
img.save(image_bytes, format=format)
return image_bytes.getvalue()
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--host", default="")
parser.add_argument("--port", type=int, default=8000)
parser.add_argument("--url_prefix", default="",
help="URL path to Boxes.py instance")
parser.add_argument("--static_url", default="static",
help="URL of static content")
args = parser.parse_args()
boxserver = BServer(url_prefix=args.url_prefix, static_url=args.static_url)
2022-11-30 15:57:58 +01:00
fc = FileChecker()
fc.start()
2024-03-31 18:51:28 +02:00
httpd = make_server(args.host, args.port, boxserver.serve)
print(f"BoxesServer serving on {args.host}:{args.port}...")
2022-11-30 15:57:58 +01:00
try:
httpd.serve_forever()
except KeyboardInterrupt:
fc.stop()
httpd.server_close()
print("BoxesServer stops.")
2024-03-31 18:51:28 +02:00
if __name__ == "__main__":
main()
else:
boxserver = BServer(url_prefix='/boxes.py', static_url="https://florianfesti.github.io/boxes/static")
application = boxserver.serve