diff options
Diffstat (limited to 'graphics/figput/javascript/server.py')
-rw-r--r-- | graphics/figput/javascript/server.py | 209 |
1 files changed, 209 insertions, 0 deletions
diff --git a/graphics/figput/javascript/server.py b/graphics/figput/javascript/server.py new file mode 100644 index 0000000000..4974d3f88c --- /dev/null +++ b/graphics/figput/javascript/server.py @@ -0,0 +1,209 @@ +""" +Use this to serve the document as it is being edited. It does several things +that no normal server does. It modifies the one and only HTML file before it +is passed to the client so that the correct document is opened. It will serve +files from directories outside the server directory. This way, the user can +keep his latex files in one place and this framework stuff in a different +directory. Also, the client can send .tikz files to the server and they +will be saved in the latex directory. Some of this (maybe all?) could be done +by taking an off-the-shelf server and tweaking it, but I want to keep things +simple, both for me and for the user. Another non-standard thing is that it +handles "WHERE" requests from the browswer; these are used so that that +documents can be loaded to open so that the are scrolled to a certain position. + +To invoke this, say + +python server.py directory/nameof.pdf port_num + +where directory/nameof.pdf is the pdf file we want to load. If the server +stuff is in top_level/something/figput_guts/ and the latex stuff is in +top_level/elsewhere/latex/ then, after cd-ing to +top_level/something/figput_guts/ you need to say + +python server.py ../../elsewhere/latex/thefile.pdf 8080 + +where the 8080 is optional, and will default to 8000. Allowing for an +optional port number permits the user to work on several documents at once. + +BUG: It is tempting to write the entire thing in a language where +I can compile it to something that uses no outside resources. Asking +the user to install external stuff is not ideal, even if it's only +Python. I suspect that this could be done most easily in Rust, although +C/C++ would work, or most anything that compiles to an executable. It +just needs to be easily compiled for mac/linux/windows without any +contortions. + +""" + +import sys +import os +import shutil +from http.server import BaseHTTPRequestHandler, HTTPServer + + +# By default, documents open at the top of the first page, but this +# can be used (see do_WHERE()) to change that. +verticalScrollPos = 0 + + +# BaseHTTPRequestHandler routes requests based on the text that appears in the +# request. So "POST" is routed to do_POST(), "GET" is routed to doGET(), and +# it would be trivial to add my own request type: "WACKY" would automatically +# be routed to do_WACKY(). That's exactly wheat is done below for the WHERE +# request. + +class MyServer(BaseHTTPRequestHandler): + + def send_head(self): + # Open and return the relevant file, or return None. + + # For whatever reason, the path that comes in always starts with a '/', + # probably to emphasize that it's relative to the "root" of the server. + # I break that concept. + # + # It is tempting to have the client reqest "draw_whatever" instead + # of "draw_whatever.js" or "draw_whatever.fjs" I could then look for + # both types here, saving a bit of effort on the js end. However, no + # normal server does this, so I would still need to maintain a version + # of the js that does ask for both types for use in a typical production + # situation. + path = self.path[1:] + f = None + if (self.path == '/'): + # Request for the one and only html file, corresponding to localhost:8000 + path = os.path.join(path,"figput.html") + else: + if (not os.path.exists(path)): + # Try the latex directory. Strip off leading '/' and pre=pend latex path. + path = self.path[1:] + path = os.path.join(args.latex_dir,path) + + try: + f = open(path, 'rb') + except OSError: + self.send_error(404, "File not found") + return None + + # The MIME type of the file *could* be ignored since "text/html" always + # seems to work, but it does generate distracting error messages in the + # broswer. + ctype = "" + base, ext = os.path.splitext(path) + if (ext == ".js"): + ctype = "text/javascript" + elif (ext == ".fjs"): + ctype = "text/javascript" + elif (ext == ".html"): + ctype = "text/html" + else: + ctype = "text/plain" + + self.send_response(200) + self.send_header("Content-type", ctype) + self.end_headers() + return f + + def do_HEAD(self): + # Probably never called in my situation, although everything this + # does must also be done for GET, so no harm in handling it. + f = self.send_head() + if f: + f.close() + + def do_GET(self): + f = self.send_head() + if f: + try: + if (self.path == '/'): + # Special case of the root html file; replace 'unknown'. + buf = f.read() + buf = buf.replace(b'unknown1',bytes(args.project_name,'utf-8') ) + buf = buf.replace(b'unknowny',bytes(str(verticalScrollPos),'utf-8') ) + self.wfile.write(buf) + else: + shutil.copyfileobj(f, self.wfile) + finally: + f.close() + + def do_POST(self): + # self.path will be '/whatever.tikz'. Strip off the leading '/' and + # pre-pend the latex directory. This is where the content is saved. + dpath = self.path[1:] + dpath = os.path.join(args.latex_dir,dpath) + text_output_file = open(dpath,"w") + + # Get the length of the incoming payload and read it all. + postLength = int(self.headers.get('content-length')) + testmsg = self.rfile.read(postLength) + + # This is semi-pointless, but testmsg is a series of bytes, and + # write() below is looking for a string. + newmsg = testmsg.decode("ASCII",'ignore') + + ignore = text_output_file.write(newmsg) + + text_output_file.flush() + text_output_file.close() + self.send_response(200, "File saved") + self.flush_headers() + + def do_WHERE(self): + # This is a trick to allow the browser to maintain the postion within the + # document when it is reloaded. It passes the current scroll position, which + # is then fed back to it when the document is reloaded. + # + # self.path is ignored. The client will send 'bogus' or the like. + # We want the contents of the message. Start off very much as for POST. + print("Got " +self.path); + postLength = int(self.headers.get('content-length')) + testmsg = self.rfile.read(postLength) + newmsg = testmsg.decode("ASCII",'ignore') + + # newmsg is the postion within the document, as a string. + global verticalScrollPos + verticalScrollPos = float(newmsg) + + +def testWhetherPDF(fpath): + + if (not os.path.exists(fpath)): + print("No such file!") + sys.exit(0) + + ftype = os.path.splitext(os.path.basename(fpath))[-1] + if (ftype.lower() != ".pdf"): + print("Not a PDF file!") + sys.exit(0); + + +import argparse + +parser = argparse.ArgumentParser() +parser.add_argument('file_path',help='specify path to pdf generated by LaTeX') +parser.add_argument('port', action='store', default=8000, type=int, + nargs='?', + help='specify alternate port (default: 8000)') +args = parser.parse_args() + +testWhetherPDF(args.file_path) + +# Parse out the directory leading to the PDF. It's used to search for js files. +# BEWARE: This is used above, as a global variable. +args.latex_dir = os.path.dirname(os.path.abspath(args.file_path)) + +# Do the same thing to get the name of the pdf file, without the '.pdf'. +# This is ultimately passed to the FigPut code so that it knows which file to +# open as the .pdf and as the .aux. +args.project_name = os.path.splitext(os.path.basename(args.file_path))[0] + +theServer = HTTPServer(("localhost", args.port), MyServer) +print(f"Server started for http://localhost:{args.port}") + +try: + theServer.serve_forever() +except KeyboardInterrupt: + pass + +theServer.server_close() +print("Http server stopped.") + |