2016-07-11 23:01:57 +02:00
#!/usr/bin/env python3
2017-02-15 15:56:02 +01:00
# 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 <http://www.gnu.org/licenses/>.
2016-03-04 12:10:59 +01:00
import sys
import argparse
import cgi
import tempfile
2016-10-08 15:08:30 +02:00
import os.path
2016-06-02 21:57:55 +02:00
import threading
import time
2017-02-15 21:50:14 +01:00
import codecs
2017-05-04 23:02:59 +02:00
import mimetypes
import re
2017-11-30 22:40:45 +01:00
import markdown
2016-05-22 12:37:35 +02:00
# Python 2 vs Python 3 compat
try:
2019-07-06 11:47:41 +02:00
from urllib.parse import unquote_plus, quote
2016-05-22 12:37:35 +02:00
except ImportError:
2019-07-06 11:47:41 +02:00
from urllib import unquote_plus, quote
2016-05-22 12:37:35 +02:00
2019-08-16 00:24:51 +02:00
try:
from urllib.parse import parse_qs
except ImportError:
from cgi import parse_qs
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:
2018-08-26 11:01:23 +02:00
sys.path.append(os.path.join(os.path.dirname(__file__), ".."))
2016-03-25 17:49:33 +01:00
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}
2017-03-22 22:01:47 +01:00
self.boxes['TrayLayout2'] = boxes.generators.traylayout.TrayLayout2(self, webargs=True)
2017-02-13 17:31:58 +01:00
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)
2016-07-31 17:18:17 +02:00
2017-05-04 23:02:59 +02:00
self.staticdir = os.path.join(os.path.dirname(__file__), '../static/')
2018-08-13 16:19:06 +02:00
def arg2html(self, a, prefix, defaults={}):
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-10-31 22:08:23 +01:00
viewname = name
if prefix and name.startswith(prefix + '_'):
viewname = name[len(prefix)+1:]
2018-08-13 16:19:06 +02:00
default = defaults.get(name, None)
2016-04-10 22:51:57 +02:00
row = """<tr><td>%s</td><td>%%s</td><td>%s</td></tr>\n""" % \
2016-10-31 22:08:23 +01:00
(viewname, a.help or "")
2016-06-21 21:51:55 +02:00
if (isinstance(a, argparse._StoreAction) and
2016-12-18 23:28:15 +01:00
hasattr(a.type, "html")):
2018-08-13 16:19:06 +02:00
input = a.type.html(name, default or a.default)
2016-04-10 22:51:57 +02:00
elif a.dest == "layout":
2018-08-27 17:11:20 +02:00
val = (default or a.default).split("\n")
2016-04-10 22:51:57 +02:00
input = """<textarea name="%s" cols="%s" rows="%s">%s</textarea>""" % \
2018-08-13 16:19:06 +02:00
(name, max((len(l) for l in val))+10, len(val)+1, default or a.default)
2016-06-27 09:09:28 +02:00
elif a.choices:
options = "\n".join(
("""<option value="%s"%s>%s</option>""" %
2018-08-13 16:19:06 +02:00
(e, ' selected="selected"' if e == (default or a.default) else "",
2016-06-27 09:09:28 +02:00
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">""" % \
2018-08-13 16:19:06 +02:00
(name, default or a.default)
2016-04-10 22:51:57 +02:00
return row % input
2016-10-31 22:08:23 +01:00
2017-02-13 17:31:58 +01:00
scripts = """
2016-10-31 22:08:23 +01:00
<script type="text/javascript">
2017-02-13 17:31:58 +01:00
function showHide(id) {
2016-10-31 22:08:23 +01:00
var e = document.getElementById(id);
2018-06-14 22:09:41 +02:00
var h = document.getElementById("h-" + id);
2016-10-31 22:08:23 +01:00
if(e.style.display == null || e.style.display == "none") {
e.style.display = "block";
2018-06-14 22:09:41 +02:00
h.classList.add("open");
2016-10-31 22:08:23 +01:00
} else {
e.style.display = "none";
2018-06-14 22:09:41 +02:00
h.classList.remove("open");
2016-10-31 22:08:23 +01:00
}
}
2016-11-01 16:58:48 +01:00
function hideargs() {
2017-02-13 17:31:58 +01:00
for ( i=0; i<%i; i++) {
2016-11-01 16:58:48 +01:00
showHide(i);
}
}
2017-02-13 17:31:58 +01:00
</script>
"""
2018-08-13 16:19:06 +02:00
def args2html(self, name, box, action="", defaults={}):
2018-03-05 19:48:09 +01:00
result = ["""<!DOCTYPE html>
<html>
<head>
<title>Boxes - """, name, """</title>
2018-09-23 14:14:19 +02:00
<link rel="icon" type="image/svg+xml" href="static/boxes-logo.svg" sizes="any">
<link rel="shortcut icon" type="image/x-icon" href="static/favicon.ico">
2017-05-04 23:09:25 +02:00
<link rel="stylesheet" href="static/self.css" type="text/css" />
""", self.scripts % (len(box.argparser._action_groups)-3), """
2018-03-05 19:49:13 +01:00
<meta name="flattr:id" content="456799">
2016-09-20 23:14:24 +02:00
</head>
2016-11-01 16:58:48 +01:00
<body onload="hideargs()">
2016-09-20 23:14:24 +02:00
<div class="container" style="background-color: #FFF8EA;">
2016-12-18 21:22:15 +01:00
<div style="float: left;">
2017-02-15 22:03:52 +01:00
<a href="./"><h1>Boxes.py</h1></a>
2016-09-20 23:14:24 +02:00
</div>
2018-09-23 14:14:19 +02:00
<div style="width: 120px; float: right;">
<img alt="self-Logo" src="static/boxes-logo.svg" width="120" >
2016-09-20 23:14:24 +02:00
</div>
<div>
<div class="clear"></div>
<hr />
2018-03-06 10:53:00 +01:00
<h2 style="margin: 0px 0px 0px 20px;" >""", name, """</h2>
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-16 10:16:56 +01:00
""" % (action)]
2016-10-31 22:08:23 +01:00
groupid = 0
2016-11-01 16:58:48 +01:00
for group in box.argparser._action_groups[3:] + box.argparser._action_groups[:3]:
2016-10-30 15:27:16 +01:00
if not group._group_actions:
2016-03-04 12:10:59 +01:00
continue
2018-10-27 13:05:02 +02:00
if len(group._group_actions) == 1 and isinstance(group._group_actions[0], argparse._HelpAction):
continue
2016-10-31 22:08:23 +01:00
prefix = getattr(group, "prefix", None)
2018-06-14 22:15:35 +02:00
result.append('''<h3 id="h-%s" class="open" onclick="showHide(%s)">%s</h3>\n<table id="%s">\n''' % (groupid, groupid, group.title, groupid))
2016-10-30 15:27:16 +01:00
for a in group._group_actions:
if a.dest in ("input", "output"):
continue
2018-08-13 16:19:06 +02:00
result.append(self.arg2html(a, prefix, defaults))
2016-10-30 15:27:16 +01:00
result.append("</table>")
2017-02-13 17:31:58 +01:00
groupid += 1
2016-10-30 15:27:16 +01:00
result.append("""
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>
2016-09-20 23:14:24 +02:00
</div>
<!--
<div style="width: 5%; float: left;"></div>
<div style="width: 35%; float: left;">
2018-03-05 19:57:32 +01:00
<img alt="sample" src="examples/box.svg" width="300" >
2016-09-20 23:14:24 +02:00
<span id="sicherheitshinweise">hier kommt dann der AJAX-Inhalt</span>
</div>
-->
<div class="clear"></div>
<hr />
2017-11-30 22:40:45 +01:00
""")
if box.description:
result.append(markdown.markdown(box.description))
result.append("""
2018-03-06 11:07:10 +01:00
</div>
2018-03-06 11:09:49 +01:00
2016-09-20 23:14:24 +02:00
<div class="footer container">
<ul>
<li><a href="https://github.com/florianfesti/boxes">Get Source at GitHub</a></li>
2018-10-01 13:53:53 +02:00
<li><a href="https://florianfesti.github.io/boxes/html/index.html">Documentation and API Description</a></li>
2016-09-20 23:14:24 +02:00
<li><a href="https://hackaday.io/project/10649-boxespy">Hackaday.io Project Page</a></li>
</ul>
</div>
2016-03-04 12:10:59 +01:00
</body>
</html>
2017-11-30 22:40:45 +01:00
""" )
2016-03-04 12:10:59 +01:00
return (s.encode("utf-8") for s in result)
2018-06-14 22:09:41 +02:00
2016-03-04 12:10:59 +01:00
def menu(self):
2018-06-14 22:09:41 +02:00
2018-03-05 19:48:09 +01:00
result = ["""<!DOCTYPE html>
<html>
<head>
<title>Boxes.py</title>
2018-09-23 14:14:19 +02:00
<link rel="icon" type="image/svg+xml" href="static/boxes-logo.svg" sizes="any">
<link rel="shortcut icon" type="image/x-icon" href="static/favicon.ico">
2017-05-04 23:09:25 +02:00
<link rel="stylesheet" href="static/self.css" type="text/css" />
2017-05-07 16:18:44 +02:00
<script>
function change(group, img_link){
document.getElementById("sample-"+group).src = img_link;
document.getElementById("sample-"+group).style.height = "auto";
}
2018-06-14 22:09:41 +02:00
2017-05-07 16:18:44 +02:00
function changeback(group){
document.getElementById("sample-" + group).src= "static/nothing.png";
document.getElementById("sample-" + group).style.height= "0px";
}
2017-02-13 17:31:58 +01:00
</script>""", self.scripts % len(self.groups), """
2018-03-05 19:49:13 +01:00
<meta name="flattr:id" content="456799">
2016-09-20 23:14:24 +02:00
</head>
2017-02-13 17:31:58 +01:00
<body onload="hideargs()">
2016-09-20 23:14:24 +02:00
<div class="container" style="background-color: #FFF8EA;">
2018-09-23 14:14:19 +02:00
<div style="width: 75%; float: left;">
2016-04-02 18:01:10 +02:00
<h1>Boxes.py</h1>
2016-03-15 21:29:03 +01:00
<p>
2017-02-15 15:56:38 +01:00
Create boxes and more with a laser cutter!
2016-03-15 21:29:03 +01:00
</p>
2017-02-15 15:56:38 +01:00
<p>
<a href="https://hackaday.io/project/10649-boxespy">Boxes.py</a> is an <a href="https://www.gnu.org/licenses/gpl-3.0.en.html">Open Source</a> box generator written in <a href="https://www.python.org/">Python</a>. 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.
2016-03-15 21:29:03 +01:00
</p>
2016-09-20 23:14:24 +02:00
</div>
2018-09-23 14:14:19 +02:00
<div style="width: 25%; float: left;">
<img alt="self-Logo" src="static/boxes-logo.svg" width="250" >
2016-09-20 23:14:24 +02:00
</div>
<div>
<div class="clear"></div>
<hr />
2017-05-07 16:18:44 +02:00
<div style="width: 100%">
2016-03-04 12:10:59 +01:00
""" ]
2017-02-13 17:31:58 +01:00
for nr, group in enumerate(self.groups):
2018-06-14 22:15:35 +02:00
result.append('''<h3 id="h-%s" class="open" onclick="showHide('%s')">%s</h3>\n<div id="%s">\n''' % (nr, nr, group.title, nr))
2017-05-07 16:18:44 +02:00
result.append("""
<div style="width: 20%%; float: right;">
2018-03-05 19:57:32 +01:00
<img style="width: 100%%;" id="sample-%s" src="static/nothing.png" alt="" />
2018-03-05 19:50:24 +01:00
</div>\n<ul>\n""" % (group.name))
2017-02-13 17:31:58 +01:00
for box in group.generators:
name = box.__class__.__name__
if name in ("TrayLayout2", ):
continue
docs = ""
if box.__doc__:
docs = " - " + box.__doc__
2017-05-07 16:18:44 +02:00
result.append(""" <li onmouseenter="change('%s', 'static/samples/%s.jpg')" onmouseleave="changeback('%s')"><a href="%s">%s</a>%s</li>\n""" % (
2018-03-05 19:50:24 +01:00
group.name, name, group.name, name, name, docs))
2017-02-13 17:31:58 +01:00
result.append("</ul>\n</div>\n")
result.append("""
2016-09-20 23:14:24 +02:00
</div>
<div style="width: 5%; float: left;"></div>
<div class="clear"></div>
<hr />
2018-03-09 16:15:52 +01:00
</div>
2018-03-09 16:22:10 +01:00
</div>
2016-09-20 23:14:24 +02:00
<div class="footer container">
2016-05-20 22:19:17 +02:00
<ul>
<li><a href="https://github.com/florianfesti/boxes">Get Source at GitHub</a></li>
2018-10-01 14:42:50 +02:00
<li><a href="https://florianfesti.github.io/boxes/html/index.html">Documentation and API Description</a></li>
2016-05-20 22:19:17 +02:00
<li><a href="https://hackaday.io/project/10649-boxespy">Hackaday.io Project Page</a></li>
2016-09-20 23:14:24 +02:00
</ul>
</div>
2018-06-14 22:09:41 +02:00
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 [
2018-03-05 19:49:13 +01:00
b"""<html>
<head>
<title>Error generating""", name.encode(),
b"""</title>
<meta name="flattr:id" content="456799">
</head>
2016-03-09 19:36:33 +01:00
<body>
2019-04-20 15:54:49 +02:00
<h1>An error occurred!</h1>""",
u"".join(u"<p>%s</p>" % cgi.escape(s) for s in type(u"")(e).split(u"\n")).encode('utf-8'),
b"""
2016-03-09 19:36:33 +01:00
</body>
</html>
""" ]
2017-05-04 23:02:59 +02:00
def serveStatic(self, environ, start_response):
filename = environ["PATH_INFO"][len("/static/"):]
path = os.path.join(self.staticdir, filename)
print(filename, path)
2017-05-04 23:09:25 +02:00
if (not re.match(r"[a-zA-Z0-9_/-]+\.[a-zA-Z0-9]+", filename) or
2017-05-04 23:02:59 +02:00
not os.path.exists(path)):
start_response("404 Not Found", [('Content-type', 'text/plain')])
return [b"Not found"]
type_, encoding = mimetypes.guess_type(filename)
if encoding is None:
encoding = "utf8"
start_response("200 OK", [('Content-type', "%s; charset=%s" % (type_, encoding))])
f = open(path, 'rb')
2017-05-07 20:29:33 +02:00
return environ['wsgi.file_wrapper'](f, 512*1024)
2017-05-04 23:02:59 +02:00
2019-07-06 11:47:41 +02:00
def getURL(self, environ):
url = environ['wsgi.url_scheme']+'://'
if environ.get('HTTP_HOST'):
url += environ['HTTP_HOST']
else:
url += environ['SERVER_NAME']
2017-05-04 23:02:59 +02:00
2019-07-06 11:47:41 +02:00
if environ['wsgi.url_scheme'] == 'https':
if environ['SERVER_PORT'] != '443':
url += ':' + environ['SERVER_PORT']
else:
if environ['SERVER_PORT'] != '80':
url += ':' + environ['SERVER_PORT']
url += quote(environ.get('SCRIPT_NAME', ''))
url += quote(environ.get('PATH_INFO', ''))
if environ.get('QUERY_STRING'):
url += '?' + environ['QUERY_STRING']
return url
def serve(self, environ, start_response):
2017-05-04 23:02:59 +02:00
if environ["PATH_INFO"].startswith("/static/"):
return self.serveStatic(environ, start_response)
2016-03-04 12:10:59 +01:00
status = '200 OK'
2018-10-01 13:43:30 +02:00
headers = [('Content-type', 'text/html; charset=utf-8'), ('X-XSS-Protection', '1; mode=block'), ('X-Content-Type-Options', 'nosniff'), ('x-frame-options', 'SAMEORIGIN'), ('Referrer-Policy', 'no-referrer')]
2017-05-04 23:02:59 +02:00
2019-08-16 00:24:51 +02:00
d = parse_qs(environ['QUERY_STRING'])
2016-03-09 19:36:33 +01:00
name = environ["PATH_INFO"][1:]
2017-05-04 23:02:59 +02:00
2016-03-09 19:36:33 +01:00
box = self.boxes.get(name, None)
if not box:
start_response(status, headers)
return self.menu()
2018-06-14 22:09:41 +02:00
2018-08-13 16:19:06 +02:00
args = [unquote_plus(arg) for arg in
environ['QUERY_STRING'].split("&")]
2018-06-14 22:09:41 +02:00
2018-08-13 16:19:06 +02:00
if "render=1" not in args:
defaults = { }
for a in args:
kv = a.split('=')
if len(kv) == 2:
k, v = kv
defaults[k] = cgi.escape(v, True)
2016-03-09 19:36:33 +01:00
start_response(status, headers)
2018-08-13 16:19:06 +02:00
return self.args2html(name, box, "./" + name, defaults=defaults)
2016-05-21 20:31:04 +02:00
else:
2018-08-13 16:19:06 +02:00
args = ["--"+ arg for arg in args if arg != "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-12-11 18:05:19 +01:00
box.parse(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-04 12:10:59 +01:00
fd, box.output = tempfile.mkstemp()
2019-07-06 11:47:41 +02:00
box.metadata["url"] = self.getURL(environ)
2019-02-08 17:43:15 +01:00
box.open()
2016-03-04 12:10:59 +01:00
box.render()
2019-08-15 23:05:21 +02:00
try:
box.close()
except ValueError as e:
start_response("500 Internal Server Error",
[('Content-type', 'text/plain; charset=utf-8')])
return([b"Server Error\n\n", str(e).encode("utf-8")])
start_response(status,
box.formats.http_headers.get(
box.format,
[('Content-type', 'application/unknown; charset=utf-8')]))
2016-03-04 12:10:59 +01:00
result = open(box.output).readlines()
os.close(fd)
2018-01-16 18:58:19 +01:00
os.remove(box.output)
2016-03-04 12:10:59 +01:00
return (l.encode("utf-8") for l in result)
if __name__=="__main__":
2017-05-04 23:09:25 +02:00
fc = FileChecker()
2016-06-02 21:57:55 +02:00
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
2016-09-20 23:14:24 +02:00
2018-06-14 22:09:41 +02:00