boxespy/scripts/boxesserver

355 lines
10 KiB
Python
Executable File

#!/usr/bin/env python3
import sys
import argparse
import cgi
import tempfile
import os.path
import threading
import time
css = (open(os.path.join(os.path.dirname(__file__), 'self.css')).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):
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>
<link rel="stylesheet" href="https://necolas.github.io/normalize.css/4.1.1/normalize.css" type="text/css" />
<style>
""", css, """
</style>
</head>
<body>
<div class="container" style="background-color: #FFF8EA;">
<div style="width: 70%; float: left;">
<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>
</div>
<div style="width: 30%; float: left;">
<img alt="self-Logo" src="https://upload.wikimedia.org/wikipedia/commons/thumb/e/e8/Gray_shaded_3D_cube.svg/2000px-Gray_shaded_3D_cube.svg.png" width="300px" >
</div>
<div>
<div class="clear"></div>
<hr />
<div style="width: 100%; float: left;">
<h2 style="font-size: 1em; margin: 0px; "> <a href="/">generators</a></h2><h2 style="margin: 0px 0px 0px 20px;" > <span style="margin-right: 10px;"> >> <span>""", name, """</h2>
<p>""", box.__doc__ or "", """</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>
</div>
<!--
<div style="width: 5%; float: left;"></div>
<div style="width: 35%; float: left;">
<img alt="sample" src="examples/box.svg" width="300px" >
<span id="sicherheitshinweise">hier kommt dann der AJAX-Inhalt</span>
</div>
-->
<div class="clear"></div>
<hr />
</div>
</div>
<div class="footer container">
<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>
</ul>
</div>
</body>
</html>
""")
return (s.encode("utf-8") for s in result)
def menu(self):
result = ["""<html>
<head><title>Boxes.py</title>
<link rel="stylesheet" href="https://necolas.github.io/normalize.css/4.1.1/normalize.css" type="text/css" />
<style>
""", css, """
</style>
<script>
function change(img_link){
var img = img_link.firstChild.innerHTML;
//alert(img);
document.getElementById("sample").src= "examples/" + img + ".svg";
}
function changeback(img_link){
document.getElementById("sample").src= "examples/" + img + ".svg";
}
</script>
</head>
<body>
<div class="container" style="background-color: #FFF8EA;">
<div style="width: 70%; float: left;">
<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>
</div>
<div style="width: 30%; float: left;">
<img alt="self-Logo" src="https://upload.wikimedia.org/wikipedia/commons/thumb/e/e8/Gray_shaded_3D_cube.svg/2000px-Gray_shaded_3D_cube.svg.png" width="300px" >
</div>
<div>
<div class="clear"></div>
<hr />
<div style="width: 100%; float: left;">
<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>
</div>
<!--
<div style="width: 5%; float: left;"></div>
<div style="width: 55%; float: left;">
<img id="sample" src="examples/box.svg" alt="Sample Image" />
</div>
-->
<div class="clear"></div>
<hr />
</div>
</div>
<div class="footer container">
<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>
</ul>
</div>
</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