#!/usr/bin/env python3 import sys import argparse import cgi import tempfile import os.path import threading import time csspath = os.path.join(os.path.dirname(__file__), 'self.css') css = (open(csspath).read()) # Python 2 vs Python 3 compat try: from urllib.parse import unquote_plus except ImportError: from urllib import unquote_plus 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.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: def __init__(self): self.boxes = {b.__name__ : b() for b in boxes.generators.getAllBoxGenerators().values() if b.webinterface} def arg2html(self, a, prefix): 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:] row = """%s%%s%s\n""" % \ (viewname, a.help or "") if (isinstance(a, argparse._StoreAction) and isinstance(a.type, boxes.ArgparseEdgeType)): input = a.type.html(name, a.default) elif a.dest == "layout": val = a.default.split("\n") input = """""" % \ (name, max((len(l) for l in val))+10, len(val)+1, a.default) elif a.type is bool: input = """""" % \ (name, ) elif a.choices: options = "\n".join( ("""""" % (e, ' selected="selected"' if e == a.default else "", e) for e in a.choices)) input = """\n""" % (name, options) else: input = """""" % \ (name, a.default) return row % input def args2html(self, name, box, action=""): result = ["""Boxes - """, name, """

Boxes.py

self-Logo

generators

>> """, name, """

""", box.__doc__ or "", """

""" % (action)] groupid = 0 for group in box.argparser._action_groups[3:] + box.argparser._action_groups[:3]: groupid += 1 if not group._group_actions: continue prefix = getattr(group, "prefix", None) result.append('

%s

\n\n' % (groupid, group.title, groupid)) for a in group._group_actions: if a.dest in ("input", "output"): continue result.append(self.arg2html(a, prefix)) result.append("
") result.append("""


""") return (s.encode("utf-8") for s in result) def menu(self): result = [""" Boxes.py

Boxes.py

A Python based generator for laser cut boxes and other things.

It features both finished parametrized generators as well as a Python API for writing your own scripts. It features finger and (flat) dovetail joints, flex cuts, holes and slots for screws and more high level functions.

self-Logo

These are the available generators:

    """ ] for name in sorted(self.boxes): if name in ("TrayLayout2", ): continue box = self.boxes[name] docs = "" if box.__doc__: docs = " - " + box.__doc__ result.append("""
  • %s%s
  • """ % ( name, name, docs)) result.append("""

""") return (s.encode("utf-8") for s in result) def errorMessage(self, name, e): return [ b"""Error generating""", name.encode(), b"""

An error occurred!

""", str(e).encode(), b"""

""" ] def serve(self, environ, start_response): status = '200 OK' headers = [('Content-type', 'text/html; charset=utf-8')] d = cgi.parse_qs(environ['QUERY_STRING']) name = environ["PATH_INFO"][1:] box = self.boxes.get(name, None) if not box: start_response(status, headers) return self.menu() args = ["--"+arg for arg in environ['QUERY_STRING'].split("&")] if "--render=1" not in args: start_response(status, headers) return self.args2html(name, box) else: args = [a for a in args if a != "--render=1"] try: box.parseArgs(args) except (ArgumentParserError) as e: start_response(status, headers) return self.errorMessage(name, e) if name == "TrayLayout": start_response(status, headers) box.fillDefault(box.x, box.y) self.boxes["TrayLayout2"].argparser.set_defaults(layout=str(box)) return self.args2html( name, self.boxes["TrayLayout2"], action="TrayLayout2") if name == "TrayLayout2": try: box.parse(unquote_plus(box.layout).split("\n")) except Exception as e: raise start_response(status, headers) return self.errorMessage(name, e) start_response(status, box.formats.http_headers.get( box.format, [('Content-type', 'application/unknown; charset=utf-8')])) fd, box.output = tempfile.mkstemp() box.render() result = open(box.output).readlines() os.remove(box.output) os.close(fd) return (l.encode("utf-8") for l in result) if __name__=="__main__": fc = FileChecker(files=[csspath]) fc.start() boxserver = BServer() httpd = make_server('', 8000, boxserver.serve) print("Serving on port 8000...") httpd.serve_forever() else: application = BServer().serve