~jonsterling/forester-devel

Simple preview server v1 SUPERSEDED

: 1
 Simple preview server

 5 files changed, 82 insertions(+), 5 deletions(-)
Export patchset (mbox)
How do I use this?

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 -3
Learn more about email & git

[PATCH] Simple preview server Export this patch

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