2016-07-11 23:01:57 +02:00
|
|
|
#!/usr/bin/env python3
|
2016-03-04 12:10:59 +01:00
|
|
|
|
|
|
|
import sys
|
|
|
|
import argparse
|
|
|
|
import cgi
|
|
|
|
import tempfile
|
|
|
|
import os
|
2016-06-02 21:57:55 +02:00
|
|
|
import threading
|
|
|
|
import time
|
2016-05-22 12:37:35 +02:00
|
|
|
|
|
|
|
# Python 2 vs Python 3 compat
|
|
|
|
try:
|
|
|
|
from urllib.parse import unquote_plus
|
|
|
|
except ImportError:
|
|
|
|
from urllib import unquote_plus
|
|
|
|
|
2016-03-04 12:10:59 +01:00
|
|
|
|
|
|
|
from wsgiref.util import setup_testing_defaults
|
|
|
|
from wsgiref.simple_server import make_server
|
|
|
|
import wsgiref.util
|
|
|
|
|
2016-03-25 17:49:33 +01:00
|
|
|
try:
|
|
|
|
import boxes.generators
|
|
|
|
except ImportError:
|
|
|
|
sys.path.append(os.path.dirname(__file__) + "/..")
|
|
|
|
import boxes.generators
|
|
|
|
|
2016-06-02 21:57:55 +02:00
|
|
|
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():
|
2016-06-30 11:54:24 +02:00
|
|
|
try:
|
|
|
|
if os.stat(path).st_mtime != timestamp:
|
|
|
|
return False
|
|
|
|
except FileNotFoundError:
|
2016-06-02 21:57:55 +02:00
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
def run(self):
|
|
|
|
while True:
|
|
|
|
if not self.filesOK():
|
|
|
|
os.execv(__file__, sys.argv)
|
|
|
|
time.sleep(1)
|
|
|
|
|
2016-03-04 12:10:59 +01:00
|
|
|
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):
|
2016-07-31 17:18:17 +02:00
|
|
|
self.boxes = {b.__name__ : b() for b in boxes.generators.getAllBoxGenerators().values() if b.webinterface}
|
|
|
|
|
2016-03-04 12:10:59 +01:00
|
|
|
def arg2html(self, a):
|
2016-03-08 21:50:47 +01:00
|
|
|
name = a.option_strings[0].replace("-", "")
|
2016-03-04 12:10:59 +01:00
|
|
|
if isinstance(a, argparse._HelpAction):
|
|
|
|
return ""
|
2016-04-10 22:51:57 +02:00
|
|
|
row = """<tr><td>%s</td><td>%%s</td><td>%s</td></tr>\n""" % \
|
|
|
|
(name, a.help or "")
|
2016-06-21 21:51:55 +02:00
|
|
|
if (isinstance(a, argparse._StoreAction) and
|
2016-04-10 22:51:57 +02:00
|
|
|
isinstance(a.type, boxes.ArgparseEdgeType)):
|
|
|
|
input = a.type.html(name, a.default)
|
|
|
|
elif a.dest == "layout":
|
2016-03-16 10:16:56 +01:00
|
|
|
val = a.default.split("\n")
|
2016-04-10 22:51:57 +02:00
|
|
|
input = """<textarea name="%s" cols="%s" rows="%s">%s</textarea>""" % \
|
|
|
|
(name, max((len(l) for l in val))+10, len(val)+1, a.default)
|
2016-06-21 21:51:55 +02:00
|
|
|
elif a.type is bool:
|
|
|
|
input = """<input name="%s" type="checkbox">""" % \
|
|
|
|
(name, )
|
2016-06-27 09:09:28 +02:00
|
|
|
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)
|
2016-04-10 22:51:57 +02:00
|
|
|
else:
|
|
|
|
input = """<input name="%s" type="text" value="%s">""" % \
|
|
|
|
(name, a.default)
|
|
|
|
|
|
|
|
return row % input
|
2016-03-04 12:10:59 +01:00
|
|
|
|
2016-03-16 10:16:56 +01:00
|
|
|
def args2html(self, name, box, action=""):
|
2016-03-09 19:36:33 +01:00
|
|
|
result = ["""<html><head><title>Boxes - """, name, """</title></head>
|
|
|
|
<body>
|
2016-03-16 11:35:13 +01:00
|
|
|
<h1>""", name, """</h1>
|
2016-07-31 17:18:17 +02:00
|
|
|
<p>""", box.__doc__ or "", """</p>
|
2016-05-21 20:31:04 +02:00
|
|
|
<form action="%s" method="GET" target="_blank">
|
2016-03-04 12:10:59 +01:00
|
|
|
<table>
|
2016-03-16 10:16:56 +01:00
|
|
|
""" % (action)]
|
2016-03-16 11:35:13 +01:00
|
|
|
for a in box.argparser._actions:
|
2016-03-16 10:16:56 +01:00
|
|
|
if a.dest in ("input", "output"):
|
2016-03-04 12:10:59 +01:00
|
|
|
continue
|
|
|
|
result.append(self.arg2html(a))
|
|
|
|
if a.dest == "burn":
|
|
|
|
result.append("</table>\n<hr>\n<table>\n")
|
|
|
|
result.append("""</table>
|
2016-05-21 20:31:04 +02:00
|
|
|
<p><button name="render" value="1">Generate</button></p>
|
2016-03-04 12:10:59 +01:00
|
|
|
</form>
|
|
|
|
</body>
|
|
|
|
</html>
|
|
|
|
""")
|
|
|
|
return (s.encode("utf-8") for s in result)
|
|
|
|
|
|
|
|
def menu(self):
|
|
|
|
result = ["""<html>
|
2016-04-02 18:01:10 +02:00
|
|
|
<head><title>Boxes.py</title></head>
|
2016-03-04 12:10:59 +01:00
|
|
|
<body>
|
2016-04-02 18:01:10 +02:00
|
|
|
<h1>Boxes.py</h1>
|
2016-03-15 21:29:03 +01:00
|
|
|
<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>
|
2016-03-04 12:10:59 +01:00
|
|
|
<ul>
|
|
|
|
""" ]
|
2016-03-08 21:52:24 +01:00
|
|
|
for name in sorted(self.boxes):
|
2016-03-16 10:16:56 +01:00
|
|
|
if name in ("TrayLayout2", ):
|
|
|
|
continue
|
2016-03-08 21:52:24 +01:00
|
|
|
box = self.boxes[name]
|
|
|
|
docs = ""
|
|
|
|
if box.__doc__:
|
|
|
|
docs = " - " + box.__doc__
|
|
|
|
result.append(""" <li><a href="%s">%s</a>%s</li>""" % (
|
|
|
|
name, name, docs))
|
2016-03-04 12:10:59 +01:00
|
|
|
result.append("""</ul>
|
2016-05-20 22:19:17 +02:00
|
|
|
<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>
|
2016-03-04 12:10:59 +01:00
|
|
|
</body>
|
|
|
|
</html>
|
|
|
|
""")
|
|
|
|
return (s.encode("utf-8") for s in result)
|
|
|
|
|
2016-03-09 19:36:33 +01:00
|
|
|
|
|
|
|
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>
|
|
|
|
""" ]
|
|
|
|
|
2016-03-04 12:10:59 +01:00
|
|
|
def serve(self, environ, start_response):
|
|
|
|
status = '200 OK'
|
|
|
|
headers = [('Content-type', 'text/html; charset=utf-8')]
|
|
|
|
|
|
|
|
d = cgi.parse_qs(environ['QUERY_STRING'])
|
2016-03-09 19:36:33 +01:00
|
|
|
name = environ["PATH_INFO"][1:]
|
|
|
|
box = self.boxes.get(name, None)
|
|
|
|
if not box:
|
|
|
|
start_response(status, headers)
|
|
|
|
return self.menu()
|
2016-03-04 12:10:59 +01:00
|
|
|
|
2016-05-21 20:31:04 +02:00
|
|
|
args = ["--"+arg for arg in environ['QUERY_STRING'].split("&")]
|
|
|
|
if "--render=1" not in args:
|
2016-03-09 19:36:33 +01:00
|
|
|
start_response(status, headers)
|
2016-03-16 11:35:13 +01:00
|
|
|
return self.args2html(name, box)
|
2016-05-21 20:31:04 +02:00
|
|
|
else:
|
|
|
|
args = [a for a in args if a != "--render=1"]
|
2016-03-04 12:10:59 +01:00
|
|
|
try:
|
|
|
|
box.parseArgs(args)
|
|
|
|
except (ArgumentParserError) as e:
|
2016-03-09 19:36:33 +01:00
|
|
|
start_response(status, headers)
|
|
|
|
return self.errorMessage(name, e)
|
2016-03-16 10:16:56 +01:00
|
|
|
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:
|
2016-05-22 12:37:35 +02:00
|
|
|
box.parse(unquote_plus(box.layout).split("\n"))
|
2016-03-16 10:16:56 +01:00
|
|
|
except Exception as e:
|
|
|
|
raise
|
|
|
|
start_response(status, headers)
|
|
|
|
return self.errorMessage(name, e)
|
|
|
|
|
2016-03-09 19:36:33 +01:00
|
|
|
start_response(status,
|
2016-06-27 09:10:00 +02:00
|
|
|
box.formats.http_headers.get(
|
|
|
|
box.format,
|
|
|
|
[('Content-type', 'application/unknown; charset=utf-8')]))
|
2016-03-04 12:10:59 +01:00
|
|
|
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__":
|
2016-06-02 21:57:55 +02:00
|
|
|
fc = FileChecker()
|
|
|
|
fc.start()
|
2016-03-04 12:10:59 +01:00
|
|
|
boxserver = BServer()
|
|
|
|
httpd = make_server('', 8000, boxserver.serve)
|
|
|
|
print("Serving on port 8000...")
|
|
|
|
httpd.serve_forever()
|
2016-05-20 20:55:02 +02:00
|
|
|
else:
|
|
|
|
application = BServer().serve
|