: 1 Simple preview server 5 files changed, 82 insertions(+), 5 deletions(-)
Copy & paste the following snippet into your terminal to import this patchset into git:
curl -s https://lists.sr.ht/~jonsterling/forester-devel/patches/56371/mbox | git am -3Learn more about email & git
Hi Jon, This patch adds a very simple preview server to forester. It doesn't do any fancy file-watching reloading; it simply rebuilds on "Enter", and then you have to refresh the page manually. At topos and at SGAI we've been using a python script for the same effect, but I thought it'd be nice to have it integrated into the binary. It is unfortunate that including dream more than doubles the binary size of forester, but my understanding is that dream is going to be included at some point anyways so this is not a huge deal. This patch depends on the previous patch I sent in for static compilation with nix. -Owen --- bin/forester/dune | 5 +++- bin/forester/main.ml | 64 ++++++++++++++++++++++++++++++++++++++++++++ dune-project | 8 +++++- flake.nix | 7 ++--- forester.opam | 3 +++ 5 files changed, 82 insertions(+), 5 deletions(-) diff --git a/bin/forester/dune b/bin/forester/dune index 3dc4189..c7c957b 100644 --- a/bin/forester/dune @@ -23,4 +23,7 @@ eio.unix bwd eio_main - fmt)) + fmt + dream + lwt + lwt_eio)) diff --git a/bin/forester/main.ml b/bin/forester/main.ml index 554f6b7..d5da2dc 100644 --- a/bin/forester/main.ml @@ -4,6 +4,7 @@ * SPDX-License-Identifier: GPL-3.0-or-later *) +open Eio.Std open Forester_prelude open Forester_core open Forester_frontend @@ -264,6 +265,68 @@ let lsp_cmd ~env = $ arg_config ) +let port_config = + let doc = "the port to run the preview server on (default 8000)" in + Arg.(value & opt int 8000 & info ["port"] ~docv: "PORT" ~doc) + +let start_server ~env port stop = + Lwt_eio.with_event_loop ~clock:env#clock @@ fun _ -> + Lwt_eio.run_lwt @@ fun () -> + Dream.serve ~stop:(Lwt_eio.run_eio stop) ~port:port @@ + Dream.router [ + Dream.get "/" @@ Dream.from_filesystem "output" "index.xml"; + Dream.get "/**" @@ Dream.static "output"; + ] + +let builder ~env config stop = + let buf = Eio.Buf_read.of_flow env#stdin ~max_size:1000 in + let rec helper _ = + let continue = Fiber.first + (fun () -> + let@ () = Forester_core.Reporter.easy_run in + stop (); + print_newline (); + Reporter.emitf Log "Received ^C, shutting down"; + false) + (fun () -> + let@ () = Forester_core.Reporter.easy_run in + Reporter.emitf Log "Rebuilding"; + let _ = build ~env config false false in + let _ = Eio.Buf_read.line buf in true) in + if continue then helper () else () in + helper () + +let preview ~env config port = + Reporter.emitf Log "Started preview server on http://localhost:%d; press enter to rebuild, Ctrl-C to stop" port; + let interrupted = Eio.Condition.create () in + let handle_signal (_signum : int) = + (* Warning: we're in a signal handler now. + Most operations are unsafe here, except for Eio.Condition.broadcast! *) + Eio.Condition.broadcast interrupted + in + Sys.set_signal Sys.sigint (Signal_handle handle_signal); + let stop = fun () -> Eio.Condition.await_no_mutex interrupted in + Fiber.both + (fun () -> builder ~env config stop) + (fun () -> start_server ~env port stop) + +let preview_cmd ~env = + let man = + [ + `S Manpage.s_description; + `P "The $(tname) command starts the forester preview server."; + ] + in + let doc = "Start the preview server" in + let info = Cmd.info "preview" ~version ~doc ~man in + Cmd.v + info + Term.( + const (preview ~env) + $ arg_config + $ port_config + ) + let cmd ~env = let doc = "a tool for tending mathematical forests" in let man = @@ -284,6 +347,7 @@ let cmd ~env = init_cmd ~env; query_cmd ~env; lsp_cmd ~env; + preview_cmd ~env; ] let () = diff --git a/dune-project b/dune-project index 5bde731..a2a6dc7 100644 --- a/dune-project +++ b/dune-project @@ -79,4 +79,10 @@ (lsp (>= 0.19.0)) (ppx_yojson_conv - (>= 0.17.0)))) + (>= 0.17.0)) + (dream + (>= 1.0.0~alpha7)) + (lwt + (>= 5.7.0)) + (lwt_eio + (>= 0.5.1)))) diff --git a/flake.nix b/flake.nix index 7be1aec..1eb2230 100644 --- a/flake.nix +++ b/flake.nix @@ -30,8 +30,8 @@ let pkgs = nixpkgs.legacyPackages.${system}; pkgsDyn = pkgs; - isGnu64 = system == "x86_64-linux"; - pkgsStatic = if isGnu64 + isLinux64 = system == "x86_64-linux"; + pkgsStatic = if isLinux64 then import nixpkgs { localSystem = nixpkgs.lib.systems.examples.gnu64; @@ -54,6 +54,7 @@ doNixSupport = false; } // (if isStatic then { buildPhase = ''dune build -p ${package} --profile static -j $NIX_BUILD_CORES''; + buildInputs = with pkgs.pkgsStatic; [ openssl libev ] ++ prev.${package}.buildInputs; } else {})); ocamlgraph = prev.ocamlgraph.overrideAttrs (_: { buildPhase = ''dune build -p ocamlgraph -j $NIX_BUILD_CORES''; @@ -66,7 +67,7 @@ main = scope'.${package}; }; scopes = mkScopes pkgs false; - scopesStatic = mkScopes pkgsStatic isGnu64; + scopesStatic = mkScopes pkgsStatic isLinux64; devPackages = builtins.attrValues (pkgs.lib.getAttrs (builtins.attrNames devPackagesQuery) scopes.scope'); in { diff --git a/forester.opam b/forester.opam index 11c9428..039691b 100644 --- a/forester.opam +++ b/forester.opam @@ -34,6 +34,9 @@ depends: [ "cid" {>= "0.1.0"} "lsp" {>= "0.19.0"} "ppx_yojson_conv" {>= "0.17.0"} + "dream" {>= "1.0.0~alpha7"} + "lwt" {>= "5.7.0"} + "lwt_eio" {>= "0.5.1"} "odoc" {with-doc} ] build: [ -- 2.47.0
Hi Owen, this looks good, but I'd like to give you some background on the refactors I am currently working on, and the direction I expect a feature like this to take in the future. I am currently reworking the compiler architecture to accomodate both the incremental and batch style compilation required for the language server and the batch build respectively. This means in particular that forester will be capable of rendering individal trees on demand without evaluating the entire forest. Once these changes land on main, I will immedately start working on a mode of use where the user directly views the emitted HTML via the http server, rather than viewing static files. This will also enable us to implement richer interactions, such as writing and running queries from the browser, full text search and other stuff. That is to say: We can apply this patch (after my refactors are done, hopefully soon), but I think it will be quickly subsumed with a more integrated solution. Regarding the choice of HTTP implementations, I have previously experimented with Cohttp because it offers native Eio integration. It seems that it will take a while for Dream to become "Eio-native". It is possible that the increase in binary size also has to do with the Lwt dependency. While I think that Dream's API is very beautiful, implementing such features (serving static assets, integrating with Dream-html) with Cohttp was only a little bit more verbose, and not more difficult, and it avoids shipping the Lwt runtime. For these reasons I will use Cohttp to implement the aforementioned features. Very excited about the static build solution! Best, Kento