#!/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 sys
import argparse
import cgi
import tempfile
import os.path
import threading
import time
import codecs
import mimetypes
import re
import markdown
import gettext
import glob
# Python 2 vs Python 3 compat
try:
from urllib.parse import unquote_plus, quote
except ImportError:
from urllib import unquote_plus, quote
try:
from urllib.parse import parse_qs
except ImportError:
from cgi import parse_qs
from wsgiref.util import setup_testing_defaults
from wsgiref.simple_server import make_server
import wsgiref.util
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 = {}
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 True:
if not self.filesOK():
os.execv(__file__, sys.argv)
time.sleep(1)
class ArgumentParserError(Exception): pass
class ThrowingArgumentParser(argparse.ArgumentParser):
def error(self, message):
raise ArgumentParserError(message)
boxes.ArgumentParser = ThrowingArgumentParser # Evil hack
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
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
%s
\n""" % \
(_(viewname), "" if not a.help else _(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, 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) else "",
_(e)) for e in a.choices))
input = """\n""" % (name, options)
else:
input = """""" % \
(name, default or a.default)
return row % input
scripts = """
"""
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 = ["""
""" + _("Boxes - %s") % _(name), """
""", self.scripts % (len(box.argparser._action_groups)-3), """
""")
if box.description:
result.append(markdown.markdown(_(box.description)))
result.append("""
""" + 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 = ["""
""" + _("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('''
\n""" % (group.name))
for box in group.generators:
name = box.__name__
if name in ("TrayLayout2", ):
continue
docs = ""
if box.__doc__:
docs = " - " + _(box.__doc__)
result.append("""