boxespy/scripts/boxesserver

253 lines
8.5 KiB
Python
Executable File

#!/usr/bin/env python3
import sys
import argparse
import cgi
import tempfile
import os
import threading
import time
# 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
from boxes.generators import box, box2, box3, drillbox
from boxes.generators import flexbox, flexbox2, flexbox3, flexbox4, gearbox
from boxes.generators import flextest, flextest2, folder, planetary, pulley
from boxes.generators import magazinefile, trayinsert, traylayout, typetray, silverwarebox
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 = {
"Box" : box.Box(),
"Box2" : box2.Box(),
"Box3" : box3.Box(),
"DrillBox" : drillbox.Box(),
"FlexBox" : flexbox.FlexBox(),
"FlexBox2" : flexbox2.FlexBox(),
"FlexBox3" : flexbox3.FlexBox(),
"FlexBox4" : flexbox4.FlexBox(),
"FlexTest": flextest.FlexTest(),
"FlexTest2": flextest2.FlexTest(),
"Folder": folder.Folder(),
"GearBox" : gearbox.GearBox(),
"MagazinFile" : magazinefile.Box(),
"TrayInsert" : trayinsert.TrayInsert(),
"PlanetaryGear" : planetary.Planetary(),
"Pulley" : pulley.Pulley(),
"TypeTray" : typetray.TypeTray(),
"SilverwareBox" : silverwarebox.Silverware(),
"TrayLayout" : traylayout.LayoutGenerator(),
"TrayLayout2" : traylayout.Layout(webargs=True),
}
def arg2html(self, a):
name = a.option_strings[0].replace("-", "")
if isinstance(a, argparse._HelpAction):
return ""
row = """<tr><td>%s</td><td>%%s</td><td>%s</td></tr>\n""" % \
(name, 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 = """<textarea name="%s" cols="%s" rows="%s">%s</textarea>""" % \
(name, max((len(l) for l in val))+10, len(val)+1, a.default)
elif a.type is bool:
input = """<input name="%s" type="checkbox">""" % \
(name, )
elif a.choices:
options = "\n".join(
("""<option value="%s"%s>%s</option>""" %
(e, ' selected="selected"' if e == a.default else "",
e) for e in a.choices))
input = """<select name="%s" size="1">\n%s</select>\n""" % (name, options)
else:
input = """<input name="%s" type="text" value="%s">""" % \
(name, a.default)
return row % input
def args2html(self, name, box, action=""):
result = ["""<html><head><title>Boxes - """, name, """</title></head>
<body>
<h1>""", name, """</h1>
<p>""", box.__doc__, """</p>
<form action="%s" method="GET" target="_blank">
<table>
""" % (action)]
for a in box.argparser._actions:
if a.dest in ("input", "output"):
continue
result.append(self.arg2html(a))
if a.dest == "burn":
result.append("</table>\n<hr>\n<table>\n")
result.append("""</table>
<p><button name="render" value="1">Generate</button></p>
</form>
</body>
</html>
""")
return (s.encode("utf-8") for s in result)
def menu(self):
result = ["""<html>
<head><title>Boxes.py</title></head>
<body>
<h1>Boxes.py</h1>
<p>
A Python based generator for laser cut boxes and other things.
</p>
<p>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.
</p>
<p>These are the available generators:</p>
<ul>
""" ]
for name in sorted(self.boxes):
if name in ("TrayLayout2", ):
continue
box = self.boxes[name]
docs = ""
if box.__doc__:
docs = " - " + box.__doc__
result.append(""" <li><a href="%s">%s</a>%s</li>""" % (
name, name, docs))
result.append("""</ul>
<ul>
<li><a href="https://github.com/florianfesti/boxes">Get Source at GitHub</a></li>
<li><a href="http://florianfesti.github.io/boxes/html/index.html">Documentation and API Description</a></li>
<li><a href="https://hackaday.io/project/10649-boxespy">Hackaday.io Project Page</a></li>
</body>
</html>
""")
return (s.encode("utf-8") for s in result)
def errorMessage(self, name, e):
return [
b"""<html><head><title>Error generating""", name.encode(),
b"""</title><head>
<body>
<h1>An error occurred!</h1>
<p>""", str(e).encode(), b"""</p>
</body>
</html>
""" ]
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()
fc.start()
boxserver = BServer()
httpd = make_server('', 8000, boxserver.serve)
print("Serving on port 8000...")
httpd.serve_forever()
else:
application = BServer().serve