├── CHANGES.md ├── test ├── dune └── tests.ml ├── .ocamlformat ├── lib ├── dune ├── dream_htmx.ml └── dream_htmx.mli ├── .gitignore ├── README.md ├── dune-project ├── LICENSE ├── dream-htmx.opam ├── .github └── workflows │ └── ci.yml └── Makefile /CHANGES.md: -------------------------------------------------------------------------------- 1 | # 0.1.0 2 | 3 | - Initial release 4 | -------------------------------------------------------------------------------- /test/dune: -------------------------------------------------------------------------------- 1 | (tests 2 | (names tests) 3 | (libraries alcotest dream dream-htmx)) 4 | -------------------------------------------------------------------------------- /.ocamlformat: -------------------------------------------------------------------------------- 1 | profile = default 2 | parse-docstrings = true 3 | wrap-comments = true 4 | -------------------------------------------------------------------------------- /lib/dune: -------------------------------------------------------------------------------- 1 | (library 2 | (name dream_htmx) 3 | (public_name dream-htmx) 4 | (libraries dream) 5 | (instrumentation 6 | (backend bisect_ppx))) 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dune generated files 2 | _build/ 3 | *.install 4 | 5 | # Local OPAM switch 6 | _opam/ 7 | 8 | # Git bisect report 9 | _coverage/ 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Dream Htmx - Utilities methods for interacting with Htmx in Dream 2 | 3 | [![Actions Status](https://github.com/beajeanm/dream-htmx/workflows/CI/badge.svg)](https://github.com/beajeanm/dream-htmx/actions) 4 | 5 | See [Htmx documentation](https://htmx.org/docs/#requests) for details about the functinality offered by theis library. 6 | The lastest documentation can be found at [https://beajeanm.github.io/dream-htmx/dream-htmx/](https://beajeanm.github.io/dream-htmx/dream-htmx/) 7 | 8 | Install with: 9 | ``` 10 | $ opam install dream-htmx 11 | ``` 12 | -------------------------------------------------------------------------------- /dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 3.0) 2 | 3 | (name dream-htmx) 4 | 5 | (documentation "https://beajeanm.github.io/dream-htmx/") 6 | 7 | (source 8 | (github beajeanm/dream-htmx)) 9 | 10 | (license ISC) 11 | 12 | (authors "Jean-Michel Bea") 13 | 14 | (maintainers "Jean-Michel Bea") 15 | 16 | (generate_opam_files true) 17 | (implicit_transitive_deps false) 18 | 19 | (package 20 | (name dream-htmx) 21 | (synopsis "Htmx utilities for Dream") 22 | (description "Htmx utilities for Dream.") 23 | (depends 24 | (ocaml (>= 4.08.0)) 25 | (dream (>= 1.0.0~alpha4)) 26 | (dune (>= 3.4.0)) 27 | (bisect_ppx (and (>= 2.5.0) :dev)) 28 | (alcotest (and (>= 1.6.0) :with-test)))) 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License (ISC) 2 | Copyright (c) 2022, Jean-Michel Bea 3 | 4 | Permission to use, copy, modify, and/or distribute this software for any 5 | purpose with or without fee is hereby granted, provided that the above 6 | copyright notice and this permission notice appear in all copies. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 9 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 10 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 11 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 12 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 13 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 14 | PERFORMANCE OF THIS SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /dream-htmx.opam: -------------------------------------------------------------------------------- 1 | # This file is generated by dune, edit dune-project instead 2 | opam-version: "2.0" 3 | synopsis: "Htmx utilities for Dream" 4 | description: "Htmx utilities for Dream." 5 | maintainer: ["Jean-Michel Bea"] 6 | authors: ["Jean-Michel Bea"] 7 | license: "ISC" 8 | homepage: "https://github.com/beajeanm/dream-htmx" 9 | doc: "https://beajeanm.github.io/dream-htmx/" 10 | bug-reports: "https://github.com/beajeanm/dream-htmx/issues" 11 | depends: [ 12 | "ocaml" {>= "4.08.0"} 13 | "dream" {>= "1.0.0~alpha4"} 14 | "dune" {>= "3.0" & >= "3.4.0"} 15 | "bisect_ppx" {>= "2.5.0" & dev} 16 | "alcotest" {>= "1.6.0" & with-test} 17 | "odoc" {with-doc} 18 | ] 19 | build: [ 20 | ["dune" "subst"] {dev} 21 | [ 22 | "dune" 23 | "build" 24 | "-p" 25 | name 26 | "-j" 27 | jobs 28 | "@install" 29 | "@runtest" {with-test} 30 | "@doc" {with-doc} 31 | ] 32 | ] 33 | dev-repo: "git+https://github.com/beajeanm/dream-htmx.git" 34 | -------------------------------------------------------------------------------- /lib/dream_htmx.ml: -------------------------------------------------------------------------------- 1 | (* Requests *) 2 | let is_htmx req = 3 | Dream.header req "HX-Request" 4 | |> Option.map String.lowercase_ascii 5 | |> Option.map (String.equal "true") 6 | |> Option.value ~default:false 7 | 8 | let trigger req = Dream.header req "HX-Trigger" 9 | let trigger_name req = Dream.header req "HX-Trigger-Name" 10 | let target req = Dream.header req "HX-Target" 11 | let prompt req = Dream.header req "HX-Prompt" 12 | 13 | (* Responses *) 14 | let push url response = Dream.set_header response "HX-Push" url 15 | let redirect url response = Dream.set_header response "HX-Redirect" url 16 | let set_location url response = Dream.set_header response "HX-Location" url 17 | let refresh response = Dream.set_header response "HX-Refresh" "true" 18 | let set_trigger event response = Dream.add_header response "HX-Trigger" event 19 | 20 | let set_trigger_after_swap event response = 21 | Dream.add_header response "HX-Trigger-After-Swap" event 22 | 23 | let set_trigger_after_settle event response = 24 | Dream.add_header response "HX-Trigger-After-Settle" event 25 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | schedule: 7 | # Prime the caches every Monday 8 | - cron: 0 1 * * MON 9 | 10 | jobs: 11 | build: 12 | name: Build and test 13 | 14 | runs-on: ${{ matrix.os }} 15 | 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | os: 20 | - ubuntu-latest 21 | ocaml-compiler: 22 | - ocaml-base-compiler.4.12.0 23 | 24 | steps: 25 | - name: Checkout code 26 | uses: actions/checkout@v2 27 | 28 | - name: Use OCaml ${{ matrix.ocaml-compiler }} 29 | uses: avsm/setup-ocaml@v2 30 | with: 31 | ocaml-compiler: ${{ matrix.ocaml-compiler }} 32 | dune-cache: ${{ matrix.os != 'macos-latest' }} 33 | opam-depext-flags: --with-test 34 | 35 | - run: opam install . --deps-only --with-test 36 | 37 | - run: opam exec -- dune build @install 38 | 39 | lint-fmt: 40 | strategy: 41 | matrix: 42 | ocaml-compiler: 43 | - ocaml-base-compiler.4.12.0 44 | 45 | runs-on: ubuntu-latest 46 | 47 | steps: 48 | - name: Checkout code 49 | uses: actions/checkout@v2 50 | 51 | - name: Use OCaml ${{ matrix.ocaml-compiler }} 52 | uses: avsm/setup-ocaml@v2 53 | with: 54 | ocaml-compiler: ${{ matrix.ocaml-compiler }} 55 | dune-cache: true 56 | 57 | - run: opam depext ocamlformat --install 58 | 59 | - run: opam install . --deps-only 60 | 61 | - run: opam exec -- dune fmt 62 | 63 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .DEFAULT_GOAL := all 2 | 3 | .PHONY: all 4 | all: 5 | opam exec -- dune build --root . @install 6 | 7 | .PHONY: deps 8 | deps: ## Install development dependencies 9 | opam install -y dune-release ocamlformat utop ocaml-lsp-server 10 | opam install --deps-only --with-test --with-doc -y . 11 | 12 | .PHONY: create_switch 13 | create_switch: 14 | opam switch create . --no-install 15 | 16 | .PHONY: switch 17 | switch: create_switch deps ## Create an opam switch and install development dependencies 18 | 19 | .PHONY: lock 20 | lock: ## Generate a lock file 21 | opam lock -y . 22 | 23 | .PHONY: build 24 | build: ## Build the project, including non installable libraries and executables 25 | opam exec -- dune build --root . 26 | 27 | .PHONY: install 28 | install: all ## Install the packages on the system 29 | opam exec -- dune install --root . 30 | 31 | .PHONY: test 32 | test: ## Run the unit tests 33 | opam exec -- dune test --root . 34 | 35 | .PHONY: clean 36 | clean: ## Clean build artifacts and other generated files 37 | opam exec -- dune clean --root . 38 | 39 | .PHONY: doc 40 | doc: ## Generate odoc documentation 41 | opam exec -- dune build --root . @doc 42 | 43 | .PHONY: fmt 44 | fmt: ## Format the codebase with ocamlformat 45 | opam exec -- dune build --root . --auto-promote @fmt 46 | 47 | .PHONY: utop 48 | utop: ## Run a REPL and link with the project's libraries 49 | opam exec -- dune utop --root . lib -- -implicit-bindings 50 | 51 | .PHONY: coverage 52 | coverage: ## Run the bisect coverage report 53 | find . -name '*.coverage' | xargs rm -f 54 | opam exec -- dune runtest --instrument-with bisect_ppx --force 55 | bisect-ppx-report html 56 | -------------------------------------------------------------------------------- /lib/dream_htmx.mli: -------------------------------------------------------------------------------- 1 | (** This module provides utilities to deal with Htmx requests/responses. 2 | 3 | @see *) 4 | 5 | (** Requests *) 6 | 7 | val is_htmx : Dream.request -> bool 8 | (** [is_htmx request] will be true if the request has been issued by htmx. *) 9 | 10 | val trigger : Dream.request -> string option 11 | (** [trigger request] gives the id of the element which trigger the request if 12 | available. *) 13 | 14 | val trigger_name : Dream.request -> string option 15 | (** [trigger_name request] gives the name of the element which trigger the 16 | request if available. *) 17 | 18 | val target : Dream.request -> string option 19 | (** [target request] gives the id of the target element if available. *) 20 | 21 | val prompt : Dream.request -> string option 22 | (** [prompt request] is the value entered by the user when prompted via 23 | hx-prompt. *) 24 | 25 | (** Responses *) 26 | 27 | val push : string -> Dream.response -> unit 28 | (** [push url] pushes a new URL into the browser’s address bar. *) 29 | 30 | val redirect : string -> Dream.response -> unit 31 | (** [redirect url] triggers a client-side redirect to a new location. *) 32 | 33 | val set_location : string -> Dream.response -> unit 34 | (** [set_location url] triggers a client-side redirect to a new location that 35 | acts as a swap. *) 36 | 37 | val refresh : Dream.response -> unit 38 | (** [refresh] triggers a client side full refresh of the page. *) 39 | 40 | val set_trigger : string -> Dream.response -> unit 41 | (** [set_trigger event] triggers a client side event. *) 42 | 43 | val set_trigger_after_swap : string -> Dream.response -> unit 44 | (** [set_trigger_after_swap event] triggers a client side event after the swap 45 | step. *) 46 | 47 | val set_trigger_after_settle : string -> Dream.response -> unit 48 | (** [set_trigger_after_settle event] triggers a client side event after the 49 | settle step. *) 50 | -------------------------------------------------------------------------------- /test/tests.ml: -------------------------------------------------------------------------------- 1 | (* Requests Tests *) 2 | 3 | let not_htmx () = 4 | Alcotest.(check bool) "Not htmx" false (Dream_htmx.is_htmx @@ Dream.request "") 5 | 6 | let is_htmx () = 7 | Alcotest.(check bool) 8 | "Is Htmx" true 9 | (Dream_htmx.is_htmx @@ Dream.request ~headers:[ ("HX-Request", "true") ] "") 10 | 11 | let get_trigger_id () = 12 | Alcotest.(check @@ option string) 13 | "Trigger id" (Some "my_id") 14 | (Dream_htmx.trigger @@ Dream.request ~headers:[ ("HX-Trigger", "my_id") ] "") 15 | 16 | let get_trigger_name () = 17 | Alcotest.(check @@ option string) 18 | "Trigger name" (Some "my_name") 19 | (Dream_htmx.trigger_name 20 | @@ Dream.request ~headers:[ ("HX-Trigger-Name", "my_name") ] "") 21 | 22 | let get_target () = 23 | Alcotest.(check @@ option string) 24 | "Target" (Some "target_id") 25 | (Dream_htmx.target 26 | @@ Dream.request ~headers:[ ("HX-Target", "target_id") ] "") 27 | 28 | let get_prompt () = 29 | Alcotest.(check @@ option string) 30 | "Promt" (Some "the_prompt") 31 | (Dream_htmx.prompt 32 | @@ Dream.request ~headers:[ ("HX-Prompt", "the_prompt") ] "") 33 | 34 | (* Responses Tests *) 35 | 36 | let set_htmx_header fn expected_header = 37 | let response = Dream.response "" in 38 | fn response; 39 | Dream.header response expected_header 40 | 41 | let push () = 42 | Alcotest.(check @@ option string) 43 | "Header is set" (Some "my_url") 44 | (set_htmx_header (Dream_htmx.push "my_url") "HX-Push") 45 | 46 | let redirect () = 47 | Alcotest.(check @@ option string) 48 | "Header is set" (Some "my_url") 49 | (set_htmx_header (Dream_htmx.redirect "my_url") "HX-Redirect") 50 | 51 | let set_location () = 52 | Alcotest.(check @@ option string) 53 | "Header is set" (Some "my_url") 54 | (set_htmx_header (Dream_htmx.set_location "my_url") "HX-Location") 55 | 56 | let refresh () = 57 | Alcotest.(check @@ option string) 58 | "Header is set" (Some "true") 59 | (set_htmx_header Dream_htmx.refresh "HX-Refresh") 60 | 61 | let trigger () = 62 | Alcotest.(check @@ option string) 63 | "Header is set" (Some "event") 64 | (set_htmx_header (Dream_htmx.set_trigger "event") "HX-Trigger") 65 | 66 | let trigger_swap () = 67 | Alcotest.(check @@ option string) 68 | "Header is set" (Some "event") 69 | (set_htmx_header 70 | (Dream_htmx.set_trigger_after_swap "event") 71 | "HX-Trigger-After-Swap") 72 | 73 | let trigger_settle () = 74 | Alcotest.(check @@ option string) 75 | "Header is set" (Some "event") 76 | (set_htmx_header 77 | (Dream_htmx.set_trigger_after_settle "event") 78 | "HX-Trigger-After-Settle") 79 | 80 | let () = 81 | Alcotest.run "Htmx" 82 | [ 83 | ( "Requests", 84 | [ 85 | Alcotest.test_case "Default request" `Quick not_htmx; 86 | Alcotest.test_case "Is Htmx" `Quick is_htmx; 87 | Alcotest.test_case "Trigger" `Quick get_trigger_id; 88 | Alcotest.test_case "Trigger Name" `Quick get_trigger_name; 89 | Alcotest.test_case "Target" `Quick get_target; 90 | Alcotest.test_case "Prompt" `Quick get_prompt; 91 | ] ); 92 | ( "Responses", 93 | [ 94 | Alcotest.test_case "Push" `Quick push; 95 | Alcotest.test_case "Redirect" `Quick redirect; 96 | Alcotest.test_case "Location" `Quick set_location; 97 | Alcotest.test_case "Refresh" `Quick refresh; 98 | Alcotest.test_case "Trigger" `Quick trigger; 99 | Alcotest.test_case "Trigger after swap" `Quick trigger_swap; 100 | Alcotest.test_case "Trigger after settle" `Quick trigger_settle; 101 | ] ); 102 | ] 103 | --------------------------------------------------------------------------------