From c96fd6b3e2670221e96ede8328275f5585375ff2 Mon Sep 17 00:00:00 2001 From: Sean Hess Date: Fri, 15 May 2026 11:27:56 -0700 Subject: [PATCH 01/10] works, but not HLS cleanup --- docs/Main.hs | 27 +++++++++++++++------------ docs/{docgen.cabal => docs.cabal} | 13 ++++++------- docs/package.yaml | 5 ++--- hyperbole.cabal | 4 +++- package.yaml | 3 +++ 5 files changed, 29 insertions(+), 23 deletions(-) rename docs/{docgen.cabal => docs.cabal} (91%) diff --git a/docs/Main.hs b/docs/Main.hs index 3bc67f1f..c2566a36 100644 --- a/docs/Main.hs +++ b/docs/Main.hs @@ -8,23 +8,25 @@ import Data.String.Conversions (cs) import Data.Text (Text) import Data.Text qualified as T import Data.Text.IO qualified as T -import Distribution.Simple.Utils (copyDirectoryRecursive) -import Distribution.Verbosity (verbose) import System.Directory import System.FilePath -import Web.Hyperbole.Data.URI --- import Control.Applicative ((<|>)) --- import Web.Hyperbole.Route (matchRoute) +import Network.URI (URI) +import Network.URI.Static (uri) +import System.Environment (getArgs) main :: IO () main = do - let tmpDir = "/tmp/hyperbole" - copyExtraFilesTo tmpDir - expandSourcesTo tmpDir - putStrLn $ "COPY RECURSIVE: " <> (tmpDir <> "docs") - copyDirectoryRecursive verbose "./docs" (tmpDir "docs") - copyDirectoryRecursive verbose "./demo" (tmpDir "demo") + args <- getArgs + case args of + [original, input, output] -> do + src <- readSource input + expanded <- expandFile src + let linePragma = T.pack $ "{-# LINE 1 \"" ++ original ++ "\" #-}" + let final = SourceCode [linePragma] <> expanded + T.writeFile output $ T.unlines final.lines + + _ -> error "Usage: docgen _ _ _" test :: IO () @@ -111,6 +113,7 @@ data Macro -- | Example Path deriving (Eq) newtype SourceCode = SourceCode {lines :: [Text]} + deriving newtype (Monoid, Semigroup) instance Show Macro where -- show (Example p) = "Example " <> show p show (Embed mn def) = "Embed " <> show mn <> " " <> show def @@ -191,7 +194,7 @@ modulePath (ModuleName mn) = cs $ T.replace "." "/" mn <> ".hs" expandEmbed :: ModuleName -> Text -> TopLevelDefinition -> IO [Text] expandEmbed mn pfx def = do let src = modulePath mn - putStrLn $ " embed: " <> src + -- putStrLn $ " embed: " <> src source <- T.readFile $ "./demo/" <> src expanded <- requireTopLevel def (SourceCode $ T.lines source) pure $ fmap markupLine expanded diff --git a/docs/docgen.cabal b/docs/docs.cabal similarity index 91% rename from docs/docgen.cabal rename to docs/docs.cabal index 40fb02ec..2dbc0e65 100644 --- a/docs/docgen.cabal +++ b/docs/docs.cabal @@ -1,10 +1,10 @@ cabal-version: 2.2 --- This file has been generated from package.yaml by hpack version 0.37.0. +-- This file has been generated from package.yaml by hpack version 0.39.5. -- -- see: https://github.com/sol/hpack -name: docgen +name: docs version: 0.6.1 synopsis: Interactive HTML apps using type-safe serverside Haskell description: Interactive HTML applications using type-safe serverside Haskell. Inspired by HTMX, Elm, and Phoenix LiveView @@ -23,9 +23,9 @@ source-repository head executable docgen main-is: Main.hs other-modules: - Paths_docgen + Paths_docs autogen-modules: - Paths_docgen + Paths_docs hs-source-dirs: ./ default-extensions: @@ -39,12 +39,11 @@ executable docgen DeriveAnyClass ghc-options: -Wall -fdefer-typed-holes build-depends: - Cabal - , base + base , casing , directory , filepath - , hyperbole + , network-uri , string-conversions , text default-language: GHC2021 diff --git a/docs/package.yaml b/docs/package.yaml index 2d77fc92..83b78c7e 100644 --- a/docs/package.yaml +++ b/docs/package.yaml @@ -1,4 +1,4 @@ -name: docgen +name: docs version: 0.6.1 synopsis: Interactive HTML apps using type-safe serverside Haskell homepage: https://github.com/seanhess/hyperbole @@ -31,8 +31,7 @@ dependencies: - filepath - text - string-conversions - - Cabal - - hyperbole + - network-uri - casing executables: diff --git a/hyperbole.cabal b/hyperbole.cabal index 8af1d478..af432314 100644 --- a/hyperbole.cabal +++ b/hyperbole.cabal @@ -93,7 +93,9 @@ library DataKinds DerivingStrategies DeriveAnyClass - ghc-options: -Wall -fdefer-typed-holes + ghc-options: -Wall -fdefer-typed-holes -F -pgmF=docgen + build-tool-depends: + docs:docgen build-depends: aeson >=2.1.2.1 && <2.3 , atomic-css ==0.2.* diff --git a/package.yaml b/package.yaml index 57fa3112..c12adbf6 100644 --- a/package.yaml +++ b/package.yaml @@ -73,6 +73,9 @@ dependencies: - http-client-tls >= 0.3 && < 0.4 library: + build-tools: docs:docgen + ghc-options: + - -F -pgmF=docgen source-dirs: - src From 5bd041e33e69d4d901bbeac8f9c0c123391e1a15 Mon Sep 17 00:00:00 2001 From: Sean Hess Date: Mon, 18 May 2026 10:16:55 -0700 Subject: [PATCH 02/10] revert docs/docgen naming to avoid breaking CI --- docs/{docs.cabal => docgen.cabal} | 6 +++--- docs/package.yaml | 2 +- hyperbole.cabal | 2 +- package.yaml | 3 ++- 4 files changed, 7 insertions(+), 6 deletions(-) rename docs/{docs.cabal => docgen.cabal} (95%) diff --git a/docs/docs.cabal b/docs/docgen.cabal similarity index 95% rename from docs/docs.cabal rename to docs/docgen.cabal index 2dbc0e65..734ffbd2 100644 --- a/docs/docs.cabal +++ b/docs/docgen.cabal @@ -4,7 +4,7 @@ cabal-version: 2.2 -- -- see: https://github.com/sol/hpack -name: docs +name: docgen version: 0.6.1 synopsis: Interactive HTML apps using type-safe serverside Haskell description: Interactive HTML applications using type-safe serverside Haskell. Inspired by HTMX, Elm, and Phoenix LiveView @@ -23,9 +23,9 @@ source-repository head executable docgen main-is: Main.hs other-modules: - Paths_docs + Paths_docgen autogen-modules: - Paths_docs + Paths_docgen hs-source-dirs: ./ default-extensions: diff --git a/docs/package.yaml b/docs/package.yaml index 83b78c7e..552659b6 100644 --- a/docs/package.yaml +++ b/docs/package.yaml @@ -1,4 +1,4 @@ -name: docs +name: docgen version: 0.6.1 synopsis: Interactive HTML apps using type-safe serverside Haskell homepage: https://github.com/seanhess/hyperbole diff --git a/hyperbole.cabal b/hyperbole.cabal index af432314..debd7cbf 100644 --- a/hyperbole.cabal +++ b/hyperbole.cabal @@ -95,7 +95,7 @@ library DeriveAnyClass ghc-options: -Wall -fdefer-typed-holes -F -pgmF=docgen build-tool-depends: - docs:docgen + docgen:docgen build-depends: aeson >=2.1.2.1 && <2.3 , atomic-css ==0.2.* diff --git a/package.yaml b/package.yaml index c12adbf6..532a33b6 100644 --- a/package.yaml +++ b/package.yaml @@ -10,6 +10,7 @@ maintainer: seanhess@gmail.com category: Web, Network description: Interactive HTML applications with type-safe serverside Haskell. Inspired by HTMX, Elm, and Phoenix LiveView. + extra-doc-files: - README.md - CHANGELOG.md @@ -73,7 +74,7 @@ dependencies: - http-client-tls >= 0.3 && < 0.4 library: - build-tools: docs:docgen + build-tools: docgen:docgen ghc-options: - -F -pgmF=docgen source-dirs: From 7ba28445f5073b8c6b6ca4054cb64492f811a011 Mon Sep 17 00:00:00 2001 From: Sean Hess Date: Mon, 18 May 2026 10:57:01 -0700 Subject: [PATCH 03/10] dev docs --- .packcheck.ignore | 1 - docs/dev.md | 8 ++++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.packcheck.ignore b/.packcheck.ignore index f35aa4ad..2be21f18 100644 --- a/.packcheck.ignore +++ b/.packcheck.ignore @@ -16,7 +16,6 @@ hyperbole-oauth2/ .gitignore .hlint.yaml .packcheck.ignore -DOCTODO.md Dockerfile bin/dev bin/docgen diff --git a/docs/dev.md b/docs/dev.md index 9caaf0a1..da655ae1 100644 --- a/docs/dev.md +++ b/docs/dev.md @@ -46,6 +46,14 @@ cd /hyperbole cabal run demo ``` +### Haskell Language Server + +We use a custom preprocessor to embed compiler-checked examples into Haddock. Everything is automatic with Cabal, but HLS doesn't support it yet. Run this command to get HLS working: + +``` +cabal install docgen:docgen --overwrite-policy=always +``` + ### Tests ``` From d77e5f57cc3ce0f26558000ca03da6ea4f7fadf7 Mon Sep 17 00:00:00 2001 From: Sean Hess Date: Tue, 19 May 2026 08:34:02 -0700 Subject: [PATCH 04/10] better docs --- README.md | 5 +-- docs/Main.hs | 97 +++++++++++++++++++++++++--------------------------- docs/dev.md | 39 +++++++++------------ 3 files changed, 65 insertions(+), 76 deletions(-) diff --git a/README.md b/README.md index ca31b9b9..9ff1d331 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Create interactive HTML applications with type-safe serverside Haskell. Inspired module Main where import Data.Text (Text) -import Web.Atomic.CSS +import Web.Atomic.CSS (border, (~)) import Web.Hyperbole main :: IO () @@ -78,6 +78,7 @@ Add hyperbole and text as dependencies to the `.cabal` file: build-depends: base , hyperbole + , atomic-css , text default-language: GHC2021 @@ -97,8 +98,8 @@ Learn More -* [Local Development](./docs/dev.md) * [Comparison with Similar Frameworks](./docs/comparison.md) +* [Local Development](./docs/dev.md) * [Using NIX](./docs/nix.md) In the Wild diff --git a/docs/Main.hs b/docs/Main.hs index c2566a36..c03f8022 100644 --- a/docs/Main.hs +++ b/docs/Main.hs @@ -1,8 +1,5 @@ -{-# LANGUAGE QuasiQuotes #-} - module Main where -import Control.Exception (SomeException, try) import Data.Char (isAlpha, isSpace) import Data.String.Conversions (cs) import Data.Text (Text) @@ -10,8 +7,6 @@ import Data.Text qualified as T import Data.Text.IO qualified as T import System.Directory import System.FilePath -import Network.URI (URI) -import Network.URI.Static (uri) import System.Environment (getArgs) @@ -36,34 +31,34 @@ test = do mapM_ print lns -expandSourcesTo :: FilePath -> IO () -expandSourcesTo tmpDir = do - allFiles <- relativeSourceFiles "./src" - -- mapM_ (putStrLn . ("SOURCE " <>)) allFiles - mapM_ (expandAndCopyFileTo tmpDir) allFiles +-- expandSourcesTo :: FilePath -> IO () +-- expandSourcesTo tmpDir = do +-- allFiles <- relativeSourceFiles "./src" +-- -- mapM_ (putStrLn . ("SOURCE " <>)) allFiles +-- mapM_ (expandAndCopyFileTo tmpDir) allFiles -copyExtraFilesTo :: FilePath -> IO () -copyExtraFilesTo tmpDir = do - createDirectoryIfMissing True tmpDir - copyFile "./cabal.project" (tmpDir "cabal.project") - copyFile "./hyperbole.cabal" (tmpDir "hyperbole.cabal") - copyFile "./README.md" (tmpDir "README.md") - copyFile "./CHANGELOG.md" (tmpDir "CHANGELOG.md") - copyFile "./LICENSE" (tmpDir "LICENSE") - createDirectoryIfMissing True (tmpDir "client/dist") - copyFile "./client/dist/hyperbole.js" (tmpDir "client/dist/hyperbole.js") - copyFile "./client/dist/hyperbole.js.map" (tmpDir "client/dist/hyperbole.js.map") - createDirectoryIfMissing True (tmpDir "client/util") - copyFile "./client/util/live-reload.js" (tmpDir "client/util/live-reload.js") +-- copyExtraFilesTo :: FilePath -> IO () +-- copyExtraFilesTo tmpDir = do +-- createDirectoryIfMissing True tmpDir +-- copyFile "./cabal.project" (tmpDir "cabal.project") +-- copyFile "./hyperbole.cabal" (tmpDir "hyperbole.cabal") +-- copyFile "./README.md" (tmpDir "README.md") +-- copyFile "./CHANGELOG.md" (tmpDir "CHANGELOG.md") +-- copyFile "./LICENSE" (tmpDir "LICENSE") +-- createDirectoryIfMissing True (tmpDir "client/dist") +-- copyFile "./client/dist/hyperbole.js" (tmpDir "client/dist/hyperbole.js") +-- copyFile "./client/dist/hyperbole.js.map" (tmpDir "client/dist/hyperbole.js.map") +-- createDirectoryIfMissing True (tmpDir "client/util") +-- copyFile "./client/util/live-reload.js" (tmpDir "client/util/live-reload.js") -expandAndCopyFileTo :: FilePath -> FilePath -> IO () -expandAndCopyFileTo tmpDir pth = do - putStrLn $ "EXPANDING " <> pth - src <- readSource pth - expanded <- expandFile src - writeSource tmpDir pth expanded +-- expandAndCopyFileTo :: FilePath -> FilePath -> IO () +-- expandAndCopyFileTo tmpDir pth = do +-- putStrLn $ "EXPANDING " <> pth +-- src <- readSource pth +-- expanded <- expandFile src +-- writeSource tmpDir pth expanded readSource :: FilePath -> IO SourceCode @@ -83,26 +78,26 @@ writeSource tmpDir relPath src = do dropWhile (== '/') . dropWhile (== '.') -relativeSourceFiles :: FilePath -> IO [FilePath] -relativeSourceFiles dir = do - contents <- tryDirectory dir - let folders = filter isFolder contents - let files = filter isSourceFile contents - - files' <- mapM (relativeSourceFiles . addDir) folders - - pure $ fmap addDir files <> mconcat files' - where - isSourceFile pth = takeExtension pth == ".hs" - isFolder pth = takeExtension pth == "" - addDir = (dir ) - tryDirectory pth = do - res <- try $ listDirectory pth - case res of - Left (_ :: SomeException) -> do - putStrLn $ "SKIPPED" <> pth - pure [] - Right files -> pure files +-- relativeSourceFiles :: FilePath -> IO [FilePath] +-- relativeSourceFiles dir = do +-- contents <- tryDirectory dir +-- let folders = filter isFolder contents +-- let files = filter isSourceFile contents +-- +-- files' <- mapM (relativeSourceFiles . addDir) folders +-- +-- pure $ fmap addDir files <> mconcat files' +-- where +-- isSourceFile pth = takeExtension pth == ".hs" +-- isFolder pth = takeExtension pth == "" +-- addDir = (dir ) +-- tryDirectory pth = do +-- res <- try $ listDirectory pth +-- case res of +-- Left (_ :: SomeException) -> do +-- putStrLn $ "SKIPPED" <> pth +-- pure [] +-- Right files -> pure files data Macro @@ -183,8 +178,8 @@ expandLine line = do -- Nothing -> fail $ "Could not find example: " <> cs (pathToText False p) -- Just r -> pure r -exampleBaseURI :: URI -exampleBaseURI = [uri|https://hyperbole.live|] +-- exampleBaseURI :: URI +-- exampleBaseURI = [uri|https://hyperbole.live|] modulePath :: ModuleName -> FilePath diff --git a/docs/dev.md b/docs/dev.md index da655ae1..6bb6a2b3 100644 --- a/docs/dev.md +++ b/docs/dev.md @@ -1,6 +1,9 @@ Local Development ================= + +# JavaScript Client Development + Download and install [NPM](https://nodejs.org/en/download). On a mac, can be installed via homebrew: ``` @@ -14,46 +17,28 @@ cd client npm install ``` -Recommended: Use `direnv` to automatically load environment from .env - -``` -brew install direnv -direnv allow -``` - - -### Building - Build JavaScript client ``` -cd client npm run build ``` Watch for changes during development ``` -cd client npm run dev ``` -Run examples -``` -# demo needs to have demo/static and client/dist as relative paths -cd /hyperbole -cabal run demo -``` +# Haskell Server Development -### Haskell Language Server - -We use a custom preprocessor to embed compiler-checked examples into Haddock. Everything is automatic with Cabal, but HLS doesn't support it yet. Run this command to get HLS working: +Run demo locally. Then visit ``` -cabal install docgen:docgen --overwrite-policy=always +cabal run demo ``` + ### Tests ``` @@ -62,8 +47,16 @@ cabal test ### File watching -Run tests, then recompile everything on file change and restart examples +Run tests, then recompile both client and server on file change, then restart demo ``` bin/dev ``` + +### Haskell Language Server + +We use a custom preprocessor to embed compiler-checked examples into Haddock. Everything is automatic with Cabal, but HLS doesn't support it yet. Run this command to get HLS working: + +``` +cabal install docgen:docgen --overwrite-policy=always +``` From 685a0dc7507f96d97b54331f7c5b90e8c15ca315 Mon Sep 17 00:00:00 2001 From: Sean Hess Date: Wed, 20 May 2026 10:47:07 -0700 Subject: [PATCH 05/10] restore nix static executable docgen --- flake.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flake.nix b/flake.nix index bbf8ddda..350bd28f 100644 --- a/flake.nix +++ b/flake.nix @@ -223,6 +223,8 @@ ghcid pkgs.ghciwatch pkgs.hpack + # `docgen` is required by `hls`, also by running `nix flake check` to resolve `type docgen` + (pkgs.haskell.lib.justStaticExecutables ghcPkgs."ghc${version}".docgen) ]; withHoogle = true; doBenchmark = true; From 8500ba02910ea38e2cf928a4481d2271ddd04ed4 Mon Sep 17 00:00:00 2001 From: Sean Hess Date: Wed, 20 May 2026 10:49:04 -0700 Subject: [PATCH 06/10] restore nix preprocessor failure --- flake.nix | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/flake.nix b/flake.nix index 350bd28f..e898a425 100644 --- a/flake.nix +++ b/flake.nix @@ -139,14 +139,23 @@ # Merges filtered `demo` + `docs` sources into `$out`. # Needed to solve `demo/docs` -> `../docs` symlink issue Nix has before. - # Named "demo" so the store path is `/nix/store/-demo`. - # That's what the `demo` expects to resolve source files. Check `demo/App/Docs/Snippet.hs` -> `localFile` - demo-docs-src = pkgs.runCommand "demo" { } '' + # Name it "demo" to have a store path `/nix/store/-demo`. + # That's what `demo-with-docs-src` expects to resolve source files. Check `demo/App/Docs/Snippet.hs` -> `localFile` + demo-with-docs-src = pkgs.runCommand "demo" { } '' mkdir -p $out/docs cp -rL ${demo-src}/. $out/ cp -rL ${docgen-src}/. $out/docs/ ''; + # Merges library `src` + `demo` sources into `$out`. + # Needed for `docgen` preprocessor to resolve `./demo/` relative paths when building the `hyperbole` library. + hyperbole-with-demo-src = pkgs.runCommand "hyperbole" { } '' + mkdir -p $out/demo + cp -rL ${src}/. $out/ + cp -rL ${demo-src}/. $out/demo/ + ''; + + ghcVersions = [ "967" "984" @@ -160,7 +169,8 @@ value = ( pkgs.overriddenHaskellPackages."ghc${ghcVer}".extend ( hfinal: hprev: { - ${demoName} = hfinal.callCabal2nix demoName demo-docs-src { }; + ${packageName} = hfinal.callCabal2nix packageName hyperbole-with-demo-src { }; + ${demoName} = hfinal.callCabal2nix demoName demo-with-docs-src { }; docgen = hfinal.callCabal2nix "docgen" docgen-src { }; hyperbole-oauth2 = hfinal.callCabal2nix "hyperbole-oauth2" oauth2-src { }; } From 79001373bfa3205358f13b99f1331ee3014234ad Mon Sep 17 00:00:00 2001 From: Sean Hess Date: Wed, 20 May 2026 11:07:45 -0700 Subject: [PATCH 07/10] hyperbole:docgen --- bin/docgen | 10 ------- cabal.project | 1 - docs/{Main.hs => Docgen.hs} | 2 +- docs/docgen.cabal | 49 ---------------------------------- docs/package.yaml | 43 ------------------------------ hyperbole.cabal | 53 ++++++++++++++++++++++++++++++++++++- package.yaml | 9 ++++++- 7 files changed, 61 insertions(+), 106 deletions(-) delete mode 100755 bin/docgen rename docs/{Main.hs => Docgen.hs} (98%) delete mode 100644 docs/docgen.cabal delete mode 100644 docs/package.yaml diff --git a/bin/docgen b/bin/docgen deleted file mode 100755 index 4697c127..00000000 --- a/bin/docgen +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -set -e - -hpack demo -hpack docs -cabal run docs - -cd /tmp/hyperbole -cabal haddock diff --git a/cabal.project b/cabal.project index 2f98c0cf..99939e47 100644 --- a/cabal.project +++ b/cabal.project @@ -3,5 +3,4 @@ multi-repl: True packages: . ./demo/ - ./docs/ ./hyperbole-oauth2/ diff --git a/docs/Main.hs b/docs/Docgen.hs similarity index 98% rename from docs/Main.hs rename to docs/Docgen.hs index c03f8022..5ccc28f7 100644 --- a/docs/Main.hs +++ b/docs/Docgen.hs @@ -21,7 +21,7 @@ main = do let final = SourceCode [linePragma] <> expanded T.writeFile output $ T.unlines final.lines - _ -> error "Usage: docgen _ _ _" + _ -> error "Usage (Hyperbole Internal Only): docgen src/MyModule.hs /tmp/input/MyModule.hs /build/output/Original.hs" test :: IO () diff --git a/docs/docgen.cabal b/docs/docgen.cabal deleted file mode 100644 index 734ffbd2..00000000 --- a/docs/docgen.cabal +++ /dev/null @@ -1,49 +0,0 @@ -cabal-version: 2.2 - --- This file has been generated from package.yaml by hpack version 0.39.5. --- --- see: https://github.com/sol/hpack - -name: docgen -version: 0.6.1 -synopsis: Interactive HTML apps using type-safe serverside Haskell -description: Interactive HTML applications using type-safe serverside Haskell. Inspired by HTMX, Elm, and Phoenix LiveView -category: Web, Network -homepage: https://github.com/seanhess/hyperbole -bug-reports: https://github.com/seanhess/hyperbole/issues -author: Sean Hess -maintainer: seanhess@gmail.com -license: BSD-3-Clause -build-type: Simple - -source-repository head - type: git - location: https://github.com/seanhess/hyperbole - -executable docgen - main-is: Main.hs - other-modules: - Paths_docgen - autogen-modules: - Paths_docgen - hs-source-dirs: - ./ - default-extensions: - OverloadedStrings - OverloadedRecordDot - DuplicateRecordFields - NoFieldSelectors - TypeFamilies - DataKinds - DerivingStrategies - DeriveAnyClass - ghc-options: -Wall -fdefer-typed-holes - build-depends: - base - , casing - , directory - , filepath - , network-uri - , string-conversions - , text - default-language: GHC2021 diff --git a/docs/package.yaml b/docs/package.yaml deleted file mode 100644 index 552659b6..00000000 --- a/docs/package.yaml +++ /dev/null @@ -1,43 +0,0 @@ -name: docgen -version: 0.6.1 -synopsis: Interactive HTML apps using type-safe serverside Haskell -homepage: https://github.com/seanhess/hyperbole -github: seanhess/hyperbole -license: BSD-3-Clause -author: Sean Hess -maintainer: seanhess@gmail.com -category: Web, Network -description: Interactive HTML applications using type-safe serverside Haskell. Inspired by HTMX, Elm, and Phoenix LiveView - -language: GHC2021 - -ghc-options: - - -Wall - - -fdefer-typed-holes - -default-extensions: - - OverloadedStrings - - OverloadedRecordDot - - DuplicateRecordFields - - NoFieldSelectors - - TypeFamilies - - DataKinds - - DerivingStrategies - - DeriveAnyClass - -dependencies: - - base - - directory - - filepath - - text - - string-conversions - - network-uri - - casing - -executables: - docgen: - main: Main.hs - source-dirs: - - . - dependencies: [] - diff --git a/hyperbole.cabal b/hyperbole.cabal index debd7cbf..48d2bf0b 100644 --- a/hyperbole.cabal +++ b/hyperbole.cabal @@ -95,7 +95,7 @@ library DeriveAnyClass ghc-options: -Wall -fdefer-typed-holes -F -pgmF=docgen build-tool-depends: - docgen:docgen + hyperbole:docgen build-depends: aeson >=2.1.2.1 && <2.3 , atomic-css ==0.2.* @@ -128,6 +128,57 @@ library , websockets >=0.12 && <0.14 default-language: GHC2021 +executable docgen + main-is: Docgen.hs + other-modules: + Paths_hyperbole + autogen-modules: + Paths_hyperbole + hs-source-dirs: + docs + default-extensions: + OverloadedStrings + OverloadedRecordDot + DuplicateRecordFields + NoFieldSelectors + TypeFamilies + DataKinds + DerivingStrategies + DeriveAnyClass + ghc-options: -Wall -fdefer-typed-holes + build-depends: + aeson >=2.1.2.1 && <2.3 + , atomic-css ==0.2.* + , attoparsec ==0.14.* + , attoparsec-aeson >=2.1 && <2.3 + , base >=4.16 && <5 + , bytestring >=0.11 && <0.13 + , casing >=0.1.2 && <0.2 + , containers >=0.6 && <1 + , cookie >=0.4 && <0.6 + , data-default ==0.8.* + , directory >=1.2 && <1.4 + , effectful >=2.4 && <3 + , file-embed >=0.0.10 && <0.1 + , filepath >=1.4 && <2 + , http-client ==0.7.* + , http-client-tls ==0.3.* + , http-types ==0.12.* + , network >=3.1 && <4 + , network-uri >=2.6.4.1 && <2.7 + , random >=1.2 && <2 + , resourcet >=1.2 && <2 + , string-conversions ==0.4.* + , string-interpolate ==0.3.* + , text >=1.2 && <3 + , time >=1.12 && <2 + , wai >=3.2 && <4 + , wai-extra >=3.1.10 && <3.2 + , wai-websockets >=3.0 && <4 + , warp >=3.3 && <4 + , websockets >=0.12 && <0.14 + default-language: GHC2021 + test-suite test type: exitcode-stdio-1.0 main-is: Spec.hs diff --git a/package.yaml b/package.yaml index 532a33b6..d44a11cf 100644 --- a/package.yaml +++ b/package.yaml @@ -74,7 +74,7 @@ dependencies: - http-client-tls >= 0.3 && < 0.4 library: - build-tools: docgen:docgen + build-tools: hyperbole:docgen ghc-options: - -F -pgmF=docgen source-dirs: @@ -93,3 +93,10 @@ tests: dependencies: - hyperbole - skeletest + +executables: + docgen: + main: Docgen.hs + source-dirs: docs + dependencies: + - directory >= 1.2 && < 1.4 From 7981b4eebd84c7152df49f97f7033154aab28857 Mon Sep 17 00:00:00 2001 From: Sean Hess Date: Thu, 21 May 2026 10:16:39 -0700 Subject: [PATCH 08/10] wild stab in the dark to fix nix fix --- flake.nix | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/flake.nix b/flake.nix index e898a425..d25913ae 100644 --- a/flake.nix +++ b/flake.nix @@ -118,12 +118,11 @@ ]; }; - docgen-src = nix-filter.lib { + docs-src = nix-filter.lib { root = ./docs; include = [ (nix-filter.lib.matchExt "hs") (nix-filter.lib.matchExt "md") - ./docs/docgen.cabal ]; }; @@ -144,7 +143,7 @@ demo-with-docs-src = pkgs.runCommand "demo" { } '' mkdir -p $out/docs cp -rL ${demo-src}/. $out/ - cp -rL ${docgen-src}/. $out/docs/ + cp -rL ${docs-src}/. $out/docs/ ''; # Merges library `src` + `demo` sources into `$out`. @@ -171,7 +170,7 @@ hfinal: hprev: { ${packageName} = hfinal.callCabal2nix packageName hyperbole-with-demo-src { }; ${demoName} = hfinal.callCabal2nix demoName demo-with-docs-src { }; - docgen = hfinal.callCabal2nix "docgen" docgen-src { }; + docgen = hfinal.callCabal2nix "docgen" docs-src { }; hyperbole-oauth2 = hfinal.callCabal2nix "hyperbole-oauth2" oauth2-src { }; } ) From 50086cf89641e3b587aad04ebc228f8fcd698b06 Mon Sep 17 00:00:00 2001 From: jk <47693+sectore@users.noreply.github.com> Date: Tue, 26 May 2026 20:27:59 +0200 Subject: [PATCH 09/10] fix(nix): `docgen` executable --- flake.nix | 58 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/flake.nix b/flake.nix index bcc8c69a..8438d6fa 100644 --- a/flake.nix +++ b/flake.nix @@ -118,12 +118,14 @@ ]; }; - docs-src = nix-filter.lib { + docs-hs-src = nix-filter.lib { root = ./docs; - include = [ - (nix-filter.lib.matchExt "hs") - (nix-filter.lib.matchExt "md") - ]; + include = [ (nix-filter.lib.matchExt "hs") ]; + }; + + docs-md-src = nix-filter.lib { + root = ./docs; + include = [ (nix-filter.lib.matchExt "md") ]; }; oauth2-src = nix-filter.lib { @@ -139,21 +141,30 @@ # Merges filtered `demo` + `docs` sources into `$out`. # Needed to solve `demo/docs` -> `../docs` symlink issue Nix has before. # Name it "demo" to have a store path `/nix/store/-demo`. - # That's what `demo-with-docs-src` expects to resolve source files. Check `demo/App/Docs/Snippet.hs` -> `localFile` + # That's what `demo-with-docs-src` expects to resolve source files. Check `demo/App/Docs/Snippet.hs` -> `localFile` demo-with-docs-src = pkgs.runCommand "demo" { } '' mkdir -p $out/docs cp -rL ${demo-src}/. $out/ - cp -rL ${docs-src}/. $out/docs/ + cp -rL ${docs-md-src}/. $out/docs/ ''; - # Merges library `src` + `demo` sources into `$out`. - # Needed for `docgen` preprocessor to resolve `./demo/` relative paths when building the `hyperbole` library. - hyperbole-with-demo-src = pkgs.runCommand "hyperbole" { } '' - mkdir -p $out/demo + # Merges `src` + `demo` + `docs` (.hs only) for building the library. + hyperbole-with-demo-docs-src = pkgs.runCommand packageName { } '' + mkdir -p $out/demo $out/docs cp -rL ${src}/. $out/ cp -rL ${demo-src}/. $out/demo/ + cp -rL ${docs-hs-src}/. $out/docs/ ''; + # Minimal sources for building `docgen` executable only. + # Same `packageName` ("hyperbole") since it's an executable of the library itself. + # and to match the `name` in `hyperbole.cabal`. + docgen-exe-src = pkgs.runCommand packageName { } '' + mkdir -p $out/docs + cp ${./hyperbole.cabal} $out/hyperbole.cabal + cp ${./LICENSE} $out/LICENSE + cp ${docs-hs-src}/Docgen.hs $out/docs/Docgen.hs + ''; ghcVersions = [ "967" @@ -167,10 +178,27 @@ name = "ghc${ghcVer}"; value = ( pkgs.overriddenHaskellPackages."ghc${ghcVer}".extend ( - hfinal: hprev: { - ${packageName} = hfinal.callCabal2nix packageName hyperbole-with-demo-src { }; + hfinal: hprev: + let + # Build `docgen` executable (but no library) + # to break the circular dependency being a preprocessor AND executable of SAME package + docgen-exe = pkgs.haskell.lib.justStaticExecutables ( + pkgs.haskell.lib.dontCheck ( + pkgs.haskell.lib.overrideCabal (hfinal.callCabal2nix packageName docgen-exe-src { }) (_: { + buildTarget = "docgen"; + }) + ) + ); + in + { + # `docgen-exe` needs to be available in PATH for ALL build phases (incl. `haddock`). + ${packageName} = pkgs.haskell.lib.addBuildTool (hfinal.callCabal2nix packageName + hyperbole-with-demo-docs-src + { } + ) docgen-exe; + # `docgen` is needed in `shell` and `checks` (search for `.docgen` to see it). + docgen = hfinal.${packageName}; ${demoName} = hfinal.callCabal2nix demoName demo-with-docs-src { }; - docgen = hfinal.callCabal2nix "docgen" docs-src { }; hyperbole-oauth2 = hfinal.callCabal2nix "hyperbole-oauth2" oauth2-src { }; } ) @@ -188,7 +216,7 @@ hlint.enable = true; fourmolu.enable = true; hpack.enable = false; - nixfmt-rfc-style.enable = true; + nixfmt.enable = true; flake-checker = { enable = true; args = [ "--no-telemetry" ]; From 32fc2b7fd077af76eeecfe70af2de67effa0e4fb Mon Sep 17 00:00:00 2001 From: jk <47693+sectore@users.noreply.github.com> Date: Tue, 26 May 2026 20:35:53 +0200 Subject: [PATCH 10/10] fix(ci): fourmolu detected unformatted files --- docs/Docgen.hs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/docs/Docgen.hs b/docs/Docgen.hs index dc41b295..7e4f1605 100644 --- a/docs/Docgen.hs +++ b/docs/Docgen.hs @@ -6,8 +6,9 @@ import Data.Text (Text) import Data.Text qualified as T import Data.Text.IO qualified as T import System.Directory -import System.FilePath import System.Environment (getArgs) +import System.FilePath + main :: IO () main = do @@ -19,7 +20,6 @@ main = do let linePragma = T.pack $ "{-# LINE 1 \"" ++ original ++ "\" #-}" let final = SourceCode [linePragma] <> expanded T.writeFile output $ T.unlines final.lines - _ -> error "Usage (Hyperbole Internal Only): docgen src/MyModule.hs /tmp/input/MyModule.hs /build/output/Original.hs" @@ -36,7 +36,6 @@ test = do -- -- mapM_ (putStrLn . ("SOURCE " <>)) allFiles -- mapM_ (expandAndCopyFileTo tmpDir) allFiles - -- copyExtraFilesTo :: FilePath -> IO () -- copyExtraFilesTo tmpDir = do -- createDirectoryIfMissing True tmpDir @@ -51,7 +50,6 @@ test = do -- createDirectoryIfMissing True (tmpDir "client/util") -- copyFile "./client/util/live-reload.js" (tmpDir "client/util/live-reload.js") - -- expandAndCopyFileTo :: FilePath -> FilePath -> IO () -- expandAndCopyFileTo tmpDir pth = do -- putStrLn $ "EXPANDING " <> pth @@ -59,7 +57,6 @@ test = do -- expanded <- expandFile src -- writeSource tmpDir pth expanded - readSource :: FilePath -> IO SourceCode readSource pth = do inp <- T.readFile pth @@ -98,7 +95,6 @@ writeSource tmpDir relPath src = do -- pure [] -- Right files -> pure files - data Macro = Embed { moduleName :: ModuleName @@ -179,7 +175,6 @@ expandLine line = do -- exampleBaseURI :: URI -- exampleBaseURI = [uri|https://hyperbole.live|] - modulePath :: ModuleName -> FilePath modulePath (ModuleName mn) = cs $ T.replace "." "/" mn <> ".hs"