http-nu 0.15.0

The surprisingly performant, Nushell-scriptable, cross.stream-powered, Datastar-ready HTTP server that fits in your back pocket.
Documentation
# The Tao of Datastar - http-nu edition
# A port of https://github.com/1363V4/tao
#
# Run: http-nu --datastar --dev -w :3001 examples/tao/serve.nu

# breathe.
# state in the right place.
# data flows in, HTML flows out. that is all.

const script_dir = path self | path dirname

use http-nu/router *
use http-nu/datastar *
use http-nu/http *
use http-nu/html *

# in Nushell, `open` understands JSON, YAML, TOML, CSV, SQLite...
# no libraries, no boilerplate. just open.
let slides = open ($script_dir | path join data.json)

# a template is compiled once, rendered many times.
let page = .mj compile ($script_dir | path join page.html)

# keyboard navigation adapts to where we are.
# pattern matching on the shape of the data -
# four cases, four paths, no ambiguity.
def nav-js [slide: record reps: int] {
  if $reps == 100 {
    "null"
  } else {
    match [($slide.previous?) ($slide.next?)] {
      [null null] => "null"
      [null _] => $"evt.key === 'ArrowRight' ? window.location = '($slide.next)' : null"
      [_ null] => $"evt.key === 'ArrowLeft' ? window.location = '($slide.previous)' : null"
      _ => $"evt.key === 'ArrowLeft' ? window.location = '($slide.previous)' : evt.key === 'ArrowRight' ? window.location = '($slide.next)' : null"
    }
  }
}

# view as a function of state.
# data comes in through the pipe, gets shaped into context,
# rendered through the template, and sent on its way.
def render-slide [req: record name: string] {
  let slide = $slides | get $name
  let cookies = $req | cookie parse
  let reps = $cookies | get -i reps | default "0" | into int

  # each full reading, the world gets a little brighter.
  let reps = $reps | if ($name == "state") { [($in + 10) 100] | math min } else { $in }
  let light = $reps / 100

  {
    title: $slide.title
    content: $slide.content
    previous: ($slide.previous?)
    next: ($slide.next?)
    light: $light
    vt_duration: (2 - 2 * $light)
    nav_js: (nav-js $slide $reps)
    datastar_js_path: $DATASTAR_JS_PATH
  }
  # send your HTML into the world. send ....
  | .mj render $page | cookie set reps $"($reps)"
}

# a request arrives. we listen, we respond.
# each route is a pattern. the first match wins.
{|req|
  dispatch $req [
    (
      route {|req|
        if ($req.path | str starts-with "/static/") {
          {subpath: ($req.path | str replace "/static" "")}
        }
      } {|req ctx|
        .static ($script_dir | path join static) $ctx.subpath
      }
    )

    # the journey begins here.
    (
      route {path: "/"} {|req ctx|
        render-slide $req "state"
      }
    )

    # or you may arrive at any point along the way.
    (
      route {path-matches: "/:key"} {|req ctx|
        if ($ctx.key in $slides) {
          render-slide $req $ctx.key
        } else {
          P [
            "You have strayed from the path. Breathe. Find your way back, "
            (A {href: "/"} "to the tao")
          ] | metadata set { merge {'http.response': {status: 404}} }
        }
      }
    )
  ]
}