this repo has no description

Split the web REPL into its own file (#114)

Also make the Dockerfile only handle web stuff

authored by bernsteinbear.com and committed by

GitHub 92c1f1f3 e234fee0

+123 -111
-3
.args
··· 1 - -m 2 - scrapscript 3 - ...
-2
.github/workflows/ci.yml
··· 69 69 push: true 70 70 tags: ${{ steps.meta.outputs.tags }} 71 71 labels: ${{ steps.meta.outputs.labels }} 72 - - name: Try a little hello world 73 - run: docker run ${{ steps.meta.outputs.tags }} apply "1 + 2" 74 72 deploy: 75 73 runs-on: ubuntu-latest 76 74 if: |
+1 -1
.github/workflows/stage.yml
··· 22 22 23 23 - name: Deploy 24 24 id: deploy 25 - uses: superfly/fly-pr-review-apps@1.2.0 25 + uses: superfly/fly-pr-review-apps@1.2.1 26 26 with: 27 27 name: scrapscript-pr-${{ github.event.number }} 28 28 region: ewr
+5 -3
Dockerfile
··· 9 9 RUN sh bin/zip -A --delete bin/python "Lib/site-packages/*" 10 10 RUN mkdir Lib 11 11 COPY scrapscript.py Lib 12 - COPY .args . 12 + COPY webrepl.py Lib 13 13 RUN bin/ape.elf bin/python -m compileall Lib 14 14 RUN mv Lib/__pycache__/scrapscript*.pyc Lib/scrapscript.pyc 15 + RUN mv Lib/__pycache__/webrepl*.pyc Lib/webrepl.pyc 15 16 RUN rm Lib/scrapscript.py 17 + RUN rm Lib/webrepl.py 16 18 RUN cp bin/python bin/scrapscript.com 17 19 COPY style.css Lib 18 20 COPY repl.html Lib 21 + RUN printf "-m\nwebrepl\n..." > .args 19 22 RUN sh bin/zip -A -r bin/scrapscript.com Lib .args 20 23 RUN bin/ape.elf bin/assimilate bin/scrapscript.com 21 24 22 25 # Set up the container 23 - FROM scratch 26 + FROM scratch as webrepl 24 27 COPY --from=build /cosmo/bin/scrapscript.com . 25 28 EXPOSE 8000 26 29 ENTRYPOINT ["./scrapscript.com"] 27 - CMD ["repl"]
+1 -1
fly.toml
··· 22 22 memory_mb = 256 23 23 24 24 [experimental] 25 - cmd = ["serve", "--port", "8000", "--debug", "--fork"] 25 + cmd = ["--port", "8000", "--debug", "--fork"]
-101
scrapscript.py
··· 4 4 import code 5 5 import dataclasses 6 6 import enum 7 - import http.server 8 7 import json 9 8 import logging 10 9 import os 11 10 import re 12 - import socketserver 13 11 import sys 14 12 import typing 15 13 import unittest 16 - import urllib.parse 17 14 import urllib.request 18 15 from dataclasses import dataclass 19 16 from enum import auto ··· 1388 1385 if isinstance(result, EnvObject): 1389 1386 return result, ScrapMonad({**env, **result.env}) 1390 1387 return result, ScrapMonad({**env, "_": result}) 1391 - 1392 - 1393 - ASSET_DIR = os.path.dirname(__file__) 1394 - 1395 - 1396 - class ScrapReplServer(http.server.SimpleHTTPRequestHandler): 1397 - def do_GET(self) -> None: 1398 - logger.debug("GET %s", self.path) 1399 - parsed_path = urllib.parse.urlsplit(self.path) 1400 - query = urllib.parse.parse_qs(parsed_path.query) 1401 - logging.debug("PATH %s", parsed_path) 1402 - logging.debug("QUERY %s", query) 1403 - if parsed_path.path == "/repl": 1404 - return self.do_repl() 1405 - if parsed_path.path == "/eval": 1406 - try: 1407 - return self.do_eval(query) 1408 - except Exception as e: 1409 - self.send_response(400) 1410 - self.send_header("Content-type", "text/plain") 1411 - self.end_headers() 1412 - self.wfile.write(str(e).encode("utf-8")) 1413 - return 1414 - if parsed_path.path == "/style.css": 1415 - self.send_response(200) 1416 - self.send_header("Content-type", "text/css") 1417 - self.end_headers() 1418 - with open(os.path.join(ASSET_DIR, "style.css"), "rb") as f: 1419 - self.wfile.write(f.read()) 1420 - return 1421 - return self.do_404() 1422 - 1423 - def do_repl(self) -> None: 1424 - self.send_response(200) 1425 - self.send_header("Content-type", "text/html") 1426 - self.end_headers() 1427 - with open(os.path.join(ASSET_DIR, "repl.html"), "rb") as f: 1428 - self.wfile.write(f.read()) 1429 - return 1430 - 1431 - def do_404(self) -> None: 1432 - self.send_response(404) 1433 - self.send_header("Content-type", "text/html") 1434 - self.end_headers() 1435 - self.wfile.write(b"""try hitting <a href="/repl">/repl</a>""") 1436 - return 1437 - 1438 - def do_eval(self, query: Dict[str, Any]) -> None: 1439 - exp = query.get("exp") 1440 - if exp is None: 1441 - raise TypeError("Need expression to evaluate") 1442 - if len(exp) != 1: 1443 - raise TypeError("Need exactly one expression to evaluate") 1444 - exp = exp[0] 1445 - tokens = tokenize(exp) 1446 - ast = parse(tokens) 1447 - env = query.get("env") 1448 - if env is None: 1449 - env = STDLIB 1450 - else: 1451 - if len(env) != 1: 1452 - raise TypeError("Need exactly one env") 1453 - env_object = deserialize(env[0]) 1454 - assert isinstance(env_object, EnvObject) 1455 - env = env_object.env 1456 - logging.debug("env is %s", env) 1457 - monad = ScrapMonad(env) 1458 - result, next_monad = monad.bind(ast) 1459 - serialized = EnvObject(next_monad.env).serialize() 1460 - encoded = bencode(serialized) 1461 - response = {"env": encoded.decode("utf-8"), "result": str(result)} 1462 - self.send_response(200) 1463 - self.send_header("Content-type", "text/plain") 1464 - self.send_header("Cache-Control", "max-age=3600") 1465 - self.end_headers() 1466 - self.wfile.write(json.dumps(response).encode("utf-8")) 1467 - return 1468 1388 1469 1389 1470 1390 class TokenizerTests(unittest.TestCase): ··· 4555 4475 unittest.main(argv=[__file__, *args.unittest_args]) 4556 4476 4557 4477 4558 - def serve_command(args: argparse.Namespace) -> None: 4559 - if args.debug: 4560 - logging.basicConfig(level=logging.DEBUG) 4561 - server: Union[type[socketserver.TCPServer], type[socketserver.ForkingTCPServer]] 4562 - if args.fork: 4563 - server = socketserver.ForkingTCPServer 4564 - else: 4565 - server = socketserver.TCPServer 4566 - server.allow_reuse_address = True 4567 - with server(("", args.port), ScrapReplServer) as httpd: 4568 - host, port = httpd.server_address 4569 - print(f"serving at http://{host!s}:{port}") 4570 - httpd.serve_forever() 4571 - 4572 - 4573 4478 def main() -> None: 4574 4479 parser = argparse.ArgumentParser(prog="scrapscript") 4575 4480 subparsers = parser.add_subparsers(dest="command") ··· 4592 4497 apply.set_defaults(func=apply_command) 4593 4498 apply.add_argument("program") 4594 4499 apply.add_argument("--debug", action="store_true") 4595 - 4596 - serve = subparsers.add_parser("serve") 4597 - serve.set_defaults(func=serve_command) 4598 - serve.add_argument("--port", type=int, default=8000) 4599 - serve.add_argument("--debug", action="store_true") 4600 - serve.add_argument("--fork", action="store_true") 4601 4500 4602 4501 args = parser.parse_args() 4603 4502 if not args.command:
+116
webrepl.py
··· 1 + #!/usr/bin/env python3.10 2 + import argparse 3 + import logging 4 + import http.server 5 + import urllib.parse 6 + import socketserver 7 + import os 8 + import json 9 + from scrapscript import ScrapMonad, parse, tokenize, deserialize, EnvObject, bencode, STDLIB 10 + from typing import Any, Dict, Union 11 + 12 + 13 + ASSET_DIR = os.path.dirname(__file__) 14 + logger = logging.getLogger(__name__) 15 + 16 + 17 + class ScrapReplServer(http.server.SimpleHTTPRequestHandler): 18 + def do_GET(self) -> None: 19 + logger.debug("GET %s", self.path) 20 + parsed_path = urllib.parse.urlsplit(self.path) 21 + query = urllib.parse.parse_qs(parsed_path.query) 22 + logger.debug("PATH %s", parsed_path) 23 + logger.debug("QUERY %s", query) 24 + if parsed_path.path == "/repl": 25 + return self.do_repl() 26 + if parsed_path.path == "/eval": 27 + try: 28 + return self.do_eval(query) 29 + except Exception as e: 30 + self.send_response(400) 31 + self.send_header("Content-type", "text/plain") 32 + self.end_headers() 33 + self.wfile.write(str(e).encode("utf-8")) 34 + return 35 + if parsed_path.path == "/style.css": 36 + self.send_response(200) 37 + self.send_header("Content-type", "text/css") 38 + self.end_headers() 39 + with open(os.path.join(ASSET_DIR, "style.css"), "rb") as f: 40 + self.wfile.write(f.read()) 41 + return 42 + return self.do_404() 43 + 44 + def do_repl(self) -> None: 45 + self.send_response(200) 46 + self.send_header("Content-type", "text/html") 47 + self.end_headers() 48 + with open(os.path.join(ASSET_DIR, "repl.html"), "rb") as f: 49 + self.wfile.write(f.read()) 50 + return 51 + 52 + def do_404(self) -> None: 53 + self.send_response(404) 54 + self.send_header("Content-type", "text/html") 55 + self.end_headers() 56 + self.wfile.write(b"""try hitting <a href="/repl">/repl</a>""") 57 + return 58 + 59 + def do_eval(self, query: Dict[str, Any]) -> None: 60 + exp = query.get("exp") 61 + if exp is None: 62 + raise TypeError("Need expression to evaluate") 63 + if len(exp) != 1: 64 + raise TypeError("Need exactly one expression to evaluate") 65 + exp = exp[0] 66 + tokens = tokenize(exp) 67 + ast = parse(tokens) 68 + env = query.get("env") 69 + if env is None: 70 + env = STDLIB 71 + else: 72 + if len(env) != 1: 73 + raise TypeError("Need exactly one env") 74 + env_object = deserialize(env[0]) 75 + assert isinstance(env_object, EnvObject) 76 + env = env_object.env 77 + logger.debug("env is %s", env) 78 + monad = ScrapMonad(env) 79 + result, next_monad = monad.bind(ast) 80 + serialized = EnvObject(next_monad.env).serialize() 81 + encoded = bencode(serialized) 82 + response = {"env": encoded.decode("utf-8"), "result": str(result)} 83 + self.send_response(200) 84 + self.send_header("Content-type", "text/plain") 85 + self.send_header("Cache-Control", "max-age=3600") 86 + self.end_headers() 87 + self.wfile.write(json.dumps(response).encode("utf-8")) 88 + return 89 + 90 + 91 + def serve(args: argparse.Namespace) -> None: 92 + if args.debug: 93 + logging.basicConfig(level=logging.DEBUG) 94 + server: Union[type[socketserver.TCPServer], type[socketserver.ForkingTCPServer]] 95 + if args.fork: 96 + server = socketserver.ForkingTCPServer 97 + else: 98 + server = socketserver.TCPServer 99 + server.allow_reuse_address = True 100 + with server(("", args.port), ScrapReplServer) as httpd: 101 + host, port = httpd.server_address 102 + print(f"serving at http://{host!s}:{port}") 103 + httpd.serve_forever() 104 + 105 + 106 + def main() -> None: 107 + parser = argparse.ArgumentParser(prog="webrepl") 108 + parser.add_argument("--port", type=int, default=8000) 109 + parser.add_argument("--debug", action="store_true") 110 + parser.add_argument("--fork", action="store_true") 111 + args = parser.parse_args() 112 + serve(args) 113 + 114 + 115 + if __name__ == "__main__": 116 + main()