#!/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 .
import argparse
import gettext
import glob
import html
import mimetypes
import os.path
import re
import sys
import tempfile
import threading
import time
import traceback
from urllib.parse import parse_qs
from urllib.parse import unquote_plus, quote
from wsgiref.simple_server import make_server
import markdown
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=True):
super(FileChecker, self).__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):
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):
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):
while not self._stopped:
if not self.filesOK():
os.execv(__file__, sys.argv)
time.sleep(1)
def stop(self):
self._stopped = True
class ArgumentParserError(Exception): pass
class ThrowingArgumentParser(argparse.ArgumentParser):
def error(self, message):
raise ArgumentParserError(message)
# Evil hack
boxes.ArgumentParser = ThrowingArgumentParser # type: ignore
static_url = "static"
class BServer:
lang_re = re.compile(r"([a-z]{2,3}(-[-a-zA-Z0-9]*)?)\s*(;\s*q=(\d\.?\d*))?")
def __init__(self):
self.boxes = {b.__name__ : b for b in boxes.generators.getAllBoxGenerators().values() if b.webinterface}
self.boxes['TrayLayout2'] = boxes.generators.traylayout.TrayLayout2
self.groups = boxes.generators.ui_groups
self.groups_by_name = boxes.generators.ui_groups_by_name
for name, box in self.boxes.items():
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 = {}
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.dest == "layout":
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""" % (name, name, name+"_id", name+"_description", options)
else:
input = """""" % \
(name, name, name+"_id", name+"_description", default or a.default)
return row % input
scripts = """
"""
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)
if lang_name:
langparam = "?language=" + lang_name
else:
langparam = ""
result = [f"""
{_("Boxes - %s") % _(name)}
{self.scripts % (len(box.argparser._action_groups)-3)}
""")
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"]))
result.append(f'''
{self.footer(lang)}
''' )
return (s.encode("utf-8") for s in result)
def menu(self, lang):
_ = lang.gettext
lang_name = lang.info().get('language', None)
if lang_name:
langparam = "?language=" + lang_name
else:
langparam = ""
result = [f"""
{_("Boxes.py")}
""",
""" {self.scripts % len(self.groups)}
{_("Boxes.py")}
{_("Create boxes and more with a laser cutter!")}
{_('''
Boxes.py is an Open Source box generator written in Python. 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.''')}
""" ]
for nr, group in enumerate(self.groups):
result.append(f'''
{_(group.title)}
''')
for box in group.generators:
name = box.__name__
if name in ("TrayLayout2", ):
continue
docs = ""
if box.__doc__:
docs = " - " + _(box.__doc__)
result.append(f"""