├── static ├── favicon.ico ├── index.html └── index.js ├── .gitignore ├── .travis.yml ├── cabal.project ├── flake.nix ├── Makefile ├── exe └── Main.hs ├── .github └── workflows │ └── main.yml ├── LICENSE ├── assets └── style.css ├── README.md ├── miso-from-html.cabal ├── index.html ├── app └── Main.hs ├── src └── Miso │ └── From │ └── Html.hs └── flake.lock /static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haskell-miso/miso-from-html/HEAD/static/favicon.ico -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.hi 3 | *.o 4 | tags 5 | dist* 6 | result 7 | cabal.project* 8 | TAGS 9 | dist* 10 | result 11 | public/ 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: nix 2 | 3 | before_install: 4 | - nix-channel --list 5 | - nix-channel --update 6 | 7 | script: 8 | - nix-build 9 | -------------------------------------------------------------------------------- /cabal.project: -------------------------------------------------------------------------------- 1 | packages: 2 | . 3 | 4 | allow-newer: 5 | all:base 6 | 7 | source-repository-package 8 | type: git 9 | location: https://github.com/dmjio/miso 10 | tag: 872d1449cb962574274338b1e3cdc8bcf13e9852 11 | 12 | flags: +template-haskell 13 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | inputs = { 3 | miso.url = "github:dmjio/miso"; 4 | }; 5 | outputs = inputs: 6 | inputs.miso.inputs.flake-utils.lib.eachDefaultSystem (system: { 7 | devShell = inputs.miso.outputs.devShells.${system}.default; 8 | devShells.wasm = inputs.miso.outputs.devShells.${system}.wasm; 9 | devShells.ghcjs = inputs.miso.outputs.devShells.${system}.ghcjs; 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | miso-from-html 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY= update build optim 2 | 3 | all: update build optim 4 | 5 | update: 6 | wasm32-wasi-cabal update 7 | 8 | build: 9 | wasm32-wasi-cabal build app 10 | rm -rf public 11 | cp -r static public 12 | cp -rv assets/ public 13 | $(eval my_wasm=$(shell wasm32-wasi-cabal list-bin app | tail -n 1)) 14 | $(shell wasm32-wasi-ghc --print-libdir)/post-link.mjs --input $(my_wasm) \ 15 | --output public/ghc_wasm_jsffi.js 16 | cp -v $(my_wasm) public/ 17 | 18 | optim: 19 | wasm-opt -all -O2 public/app.wasm -o public/app.wasm 20 | wasm-tools strip -o public/app.wasm public/app.wasm 21 | 22 | serve: 23 | http-server public 24 | 25 | clean: 26 | rm -rf dist-newstyle public 27 | 28 | -------------------------------------------------------------------------------- /static/index.js: -------------------------------------------------------------------------------- 1 | import { WASI, OpenFile, File, ConsoleStdout } from "https://cdn.jsdelivr.net/npm/@bjorn3/browser_wasi_shim@0.3.0/dist/index.js"; 2 | import ghc_wasm_jsffi from "./ghc_wasm_jsffi.js"; 3 | 4 | const args = []; 5 | const env = ["GHCRTS=-H64m"]; 6 | const fds = [ 7 | new OpenFile(new File([])), // stdin 8 | ConsoleStdout.lineBuffered((msg) => console.log(`[WASI stdout] ''${msg}`)), 9 | ConsoleStdout.lineBuffered((msg) => console.warn(`[WASI stderr] ''${msg}`)), 10 | ]; 11 | const options = { debug: false }; 12 | const wasi = new WASI(args, env, fds, options); 13 | 14 | const instance_exports = {}; 15 | const { instance } = await WebAssembly.instantiateStreaming(fetch("app.wasm"), { 16 | wasi_snapshot_preview1: wasi.wasiImport, 17 | ghc_wasm_jsffi: ghc_wasm_jsffi(instance_exports), 18 | }); 19 | Object.assign(instance_exports, instance.exports); 20 | 21 | wasi.initialize(instance); 22 | await instance.exports.hs_start(globalThis.example); 23 | -------------------------------------------------------------------------------- /exe/Main.hs: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------------- 2 | {-# LANGUAGE LambdaCase #-} 3 | {-# LANGUAGE FlexibleContexts #-} 4 | {-# LANGUAGE OverloadedStrings #-} 5 | {-# LANGUAGE FlexibleInstances #-} 6 | {-# LANGUAGE DerivingStrategies #-} 7 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} 8 | ----------------------------------------------------------------------------- 9 | module Main (main) where 10 | ----------------------------------------------------------------------------- 11 | import qualified Data.Text.IO as T 12 | ----------------------------------------------------------------------------- 13 | import Miso.From.Html (processPretty) 14 | ----------------------------------------------------------------------------- 15 | main :: IO () 16 | main = T.putStrLn =<< processPretty <$> T.getContents 17 | ----------------------------------------------------------------------------- 18 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build and deploy 2 | 3 | on: 4 | push: 5 | branches: master 6 | pull_request: 7 | 8 | permissions: 9 | contents: read 10 | pages: write 11 | id-token: write 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v4 19 | 20 | - uses: cachix/install-nix-action@v31 21 | with: 22 | github_access_token: ${{ secrets.GITHUB_TOKEN }} 23 | 24 | - name: WASM build 25 | run: nix develop .#wasm --command bash -c "make clean && make" 26 | 27 | - name: Upload artifact 28 | uses: actions/upload-pages-artifact@v3 29 | with: 30 | path: ./public 31 | 32 | deploy: 33 | if: github.ref == 'refs/heads/master' 34 | runs-on: ubuntu-latest 35 | 36 | environment: 37 | name: github-pages 38 | url: ${{ steps.deployment.outputs.page_url }} 39 | 40 | needs: build 41 | 42 | steps: 43 | - name: Deploy to GitHub Pages 44 | id: deployment 45 | uses: actions/deploy-pages@v4 46 | 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016-2020, David M. Johnson 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 9 | 10 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 11 | -------------------------------------------------------------------------------- /assets/style.css: -------------------------------------------------------------------------------- 1 | * { 2 | box-sizing: border-box; 3 | margin: 0; 4 | padding: 0; 5 | } 6 | 7 | body { 8 | font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; 9 | line-height: 1.6; 10 | background-color: #f5f5f5; 11 | padding: 20px; 12 | } 13 | 14 | .container { 15 | display: flex; 16 | gap: 20px; 17 | max-width: 1200px; 18 | margin: 0 auto; 19 | height: calc(100vh - 40px); 20 | } 21 | 22 | .panel { 23 | flex: 1; 24 | display: flex; 25 | flex-direction: column; 26 | } 27 | 28 | .panel-header { 29 | background-color: #2c3e50; 30 | color: white; 31 | padding: 10px 15px; 32 | font-weight: bold; 33 | border-radius: 5px 5px 0 0; 34 | } 35 | 36 | .input-area { 37 | flex: 1; 38 | padding: 15px; 39 | border: 1px solid #ddd; 40 | border-top: none; 41 | background-color: white; 42 | resize: none; 43 | font-family: inherit; 44 | font-size: 14px; 45 | line-height: 1.5; 46 | border-radius: 0 0 0 5px; 47 | } 48 | 49 | .output-area { 50 | flex: 1; 51 | padding: 15px; 52 | border: 1px solid #ddd; 53 | border-top: none; 54 | background-color: #282c34; 55 | color: #abb2bf; 56 | font-family: 'Courier New', Courier, monospace; 57 | font-size: 14px; 58 | line-height: 1.5; 59 | white-space: pre; 60 | overflow: auto; 61 | border-radius: 0 0 5px 0; 62 | } 63 | 64 | @media (max-width: 768px) { 65 | .container { 66 | flex-direction: column; 67 | height: auto; 68 | } 69 | .input-area, .output-area { 70 | min-height: 300px; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 🍜 miso-from-html 2 | =================== 3 | Use [live](https://miso-from-html.haskell-miso.org) from your browser. 4 | 5 | Convert HTML into [miso](https://github.com/dmjio/miso) `View` syntax. 6 | 7 | ### Usage 8 | 9 | Given some HTML 10 | 11 | ```html 12 | 20 | ``` 21 | 22 | Convert it to [miso](https://github.com/dmjio/miso) `View` syntax. 23 | 24 | ```bash 25 | $ cabal run miso-from-html < index.html 26 | ``` 27 | 28 | Result 29 | 30 | ```haskell 31 | nav_ 32 | [ class_ "navbar" 33 | , role_ "navigation" 34 | ] 35 | [ div_ [ class_ "navbar-brand" ] 36 | [ a_ 37 | [ class_ "navbar-item" 38 | , href_ "https://bulma.io" 39 | ] 40 | [ img_ 41 | [ src_ "https://bulma.io/images/bulma-logo.png" 42 | , width_ "112" 43 | , height_ "28" 44 | ] 45 | , a_ [] 46 | [ "ok" 47 | , p_ [][ "hey" ] 48 | ] 49 | ] 50 | ] 51 | ] 52 | ``` 53 | 54 | ### Limitations 55 | 56 | Currently operates on a single top-level node, not on a list of nodes. 57 | 58 | This is invalid since there is no single top-level parent node. 59 | 60 | ```html 61 |
62 | foo 63 |
64 |
65 | bar 66 |
67 | ``` 68 | 69 | This is valid 70 | 71 | ```html 72 |
73 |
74 | foo 75 |
76 |
77 | bar 78 |
79 |
80 | ``` 81 | 82 | Also, if your HTML isn't parsing, make sure it's valid. 83 | 84 | When in doubt, check the [W3C validation service](https://validator.w3.org/#validate_by_input). 85 | 86 | ### Test 87 | 88 | ```bash 89 | nix develop --command bash -c cabal run < index.html' 90 | ``` 91 | -------------------------------------------------------------------------------- /miso-from-html.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: 3.0 2 | name: 3 | miso-from-html 4 | version: 5 | 0.3.0.0 6 | synopsis: 7 | Convert HTML to miso View syntax 8 | description: 9 | HTML parser that pretty prints to a Miso View 10 | bug-reports: 11 | https://github.com/dmjio/miso-from-html/issues 12 | license: 13 | BSD-3-Clause 14 | license-file: 15 | LICENSE 16 | author: 17 | David Johnson 18 | maintainer: 19 | code@dmj.io 20 | copyright: 21 | (c) David Johnson 2025 22 | category: 23 | Web 24 | build-type: 25 | Simple 26 | extra-source-files: 27 | README.md 28 | index.html 29 | 30 | common wasm 31 | if arch(wasm32) 32 | ghc-options: 33 | -no-hs-main -optl-mexec-model=reactor "-optl-Wl,--export=hs_start" 34 | cpp-options: 35 | -DWASM 36 | 37 | library 38 | default-language: 39 | Haskell2010 40 | ghc-options: 41 | -Wall 42 | hs-source-dirs: 43 | src 44 | exposed-modules: 45 | Miso.From.Html 46 | build-depends: 47 | base < 5, 48 | bytestring, 49 | containers, 50 | html-parse, 51 | miso, 52 | mtl, 53 | pretty-simple, 54 | text 55 | 56 | -- build w/ vanilla ghc 57 | executable miso-from-html 58 | main-is: 59 | Main.hs 60 | hs-source-dirs: 61 | exe 62 | ghc-options: 63 | -Wall 64 | build-depends: 65 | base < 5, 66 | miso-from-html, 67 | text 68 | default-language: 69 | Haskell2010 70 | 71 | -- Build w/ WASM / JS backends (or vanilla for hot-reload w/ jssaddle) 72 | executable app 73 | import: 74 | wasm 75 | main-is: 76 | Main.hs 77 | ghc-options: 78 | -Wall 79 | hs-source-dirs: 80 | app 81 | build-depends: 82 | base < 5, 83 | jsaddle, 84 | miso, 85 | miso-from-html, 86 | fourmolu 87 | 88 | default-language: 89 | Haskell2010 90 | 91 | source-repository head 92 | type: git 93 | location: https://github.com/dmjio/miso-from-html.git 94 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | Fork me on GitHub 6 |
7 |
8 | 16 |
17 |
18 |
19 |
20 | miso logo 21 |

miso

22 |

A tasty Haskell front-end framework

23 |
24 |
25 |
26 |
27 |
28 | 54 |
55 | 65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /app/Main.hs: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------------- 2 | {-# LANGUAGE CPP #-} 3 | {-# LANGUAGE TypeApplications #-} 4 | {-# LANGUAGE OverloadedStrings #-} 5 | ----------------------------------------------------------------------------- 6 | module Main (main) where 7 | ----------------------------------------------------------------------------- 8 | import Control.Monad 9 | import Control.Monad.IO.Class (liftIO) 10 | import Prelude hiding (unlines, rem) 11 | import Language.Javascript.JSaddle 12 | ----------------------------------------------------------------------------- 13 | import Miso 14 | import Miso.Html hiding (title_) 15 | import Miso.Html.Property 16 | import Miso.Navigator 17 | import Miso.Lens hiding (set) 18 | import Miso.From.Html (process) 19 | import Miso.String 20 | import qualified Miso.CSS as CSS 21 | ----------------------------------------------------------------------------- 22 | import Ormolu (ormolu) 23 | import Ormolu.Config 24 | ----------------------------------------------------------------------------- 25 | #ifdef WASM 26 | foreign export javascript "hs_start" main :: IO () 27 | #endif 28 | ----------------------------------------------------------------------------- 29 | data Mode 30 | = Editing 31 | | Clear 32 | deriving (Show, Eq) 33 | ----------------------------------------------------------------------------- 34 | data Model 35 | = Model 36 | { _value :: MisoString 37 | , _mode :: Mode 38 | } deriving (Show, Eq) 39 | ----------------------------------------------------------------------------- 40 | instance ToMisoString Model where 41 | toMisoString (Model v _) = toMisoString v 42 | ----------------------------------------------------------------------------- 43 | value :: Lens Model MisoString 44 | value = lens _value $ \m v -> m { _value = v } 45 | ----------------------------------------------------------------------------- 46 | mode :: Lens Model Mode 47 | mode = lens _mode $ \m v -> m { _mode = v } 48 | ----------------------------------------------------------------------------- 49 | data Action 50 | = OnInput MisoString 51 | | CopyToClipboard 52 | | Copied 53 | | ErrorCopy JSVal 54 | | SetText MisoString 55 | | ClearText 56 | ----------------------------------------------------------------------------- 57 | main :: IO () 58 | main = run (startApp app) 59 | ----------------------------------------------------------------------------- 60 | formatString :: MisoString -> IO MisoString 61 | formatString = fmap toMisoString . ormolu cfg "" . fromMisoString 62 | where 63 | cfg = 64 | defaultConfig 65 | { cfgPrinterOpts = 66 | defaultPrinterOpts 67 | { poColumnLimit = pure (ColumnLimit 50) 68 | } 69 | } 70 | ----------------------------------------------------------------------------- 71 | app :: App Model Action 72 | app = (component (Model mempty Clear) updateModel viewModel) 73 | #ifndef WASM 74 | { styles = 75 | [ Href "assets/style.css" 76 | , Href "https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css" 77 | ] 78 | , scripts = 79 | [ Src "https://cdn.jsdelivr.net/npm/toastify-js" 80 | ] 81 | } 82 | #endif 83 | ----------------------------------------------------------------------------- 84 | updateModel :: Action -> Transition Model Action 85 | updateModel (OnInput input) = do 86 | mode .= Editing 87 | let output = ms (process (fromMisoString input)) 88 | io (SetText <$> liftIO (formatString output)) 89 | updateModel CopyToClipboard = do 90 | input <- use value 91 | copyClipboard input Copied ErrorCopy 92 | updateModel (ErrorCopy err) = 93 | io_ (consoleLog' err) 94 | updateModel Copied = 95 | io_ (showToast "Copied to clipboard...") 96 | updateModel (SetText txt) = 97 | value .= txt 98 | updateModel ClearText = do 99 | mode .= Clear 100 | value .= mempty 101 | ----------------------------------------------------------------------------- 102 | githubStar :: View model action 103 | githubStar = iframe_ 104 | [ title_ "GitHub" 105 | , height_ "30" 106 | , width_ "170" 107 | , textProp "scrolling" "0" 108 | , textProp "frameborder" "0" 109 | , src_ 110 | "https://ghbtns.com/github-btn.html?user=haskell-miso&repo=miso-from-html&type=star&count=true&size=large" 111 | ] 112 | [] 113 | ----------------------------------------------------------------------------- 114 | viewModel :: Model -> View Model Action 115 | viewModel (Model input mode_) = 116 | div_ 117 | [] 118 | [ githubStar 119 | , div_ 120 | [ CSS.style_ [ CSS.textAlign "center" ] 121 | ] 122 | [ h1_ [] 123 | [ "🍜 miso-from-html" 124 | ] 125 | , h4_ [] 126 | [ "Convert HTML to miso" 127 | ] 128 | , button_ 129 | [ onClick ClearText 130 | , CSS.style_ 131 | [ CSS.width "120px" 132 | , CSS.height "50px" 133 | , CSS.fontSize "20px" 134 | ] 135 | ] 136 | [ "Clear" 137 | ] 138 | , button_ 139 | [ onClick CopyToClipboard 140 | , CSS.style_ 141 | [ CSS.width "120px" 142 | , CSS.height "50px" 143 | , CSS.margin "5px" 144 | , CSS.fontSize "20px" 145 | ] 146 | ] 147 | [ "Copy" 148 | ] 149 | ] 150 | , div_ 151 | [ className "container" 152 | ] 153 | [ div_ 154 | [ class_ "panel" 155 | ] 156 | [ div_ 157 | [ class_ "panel-header" 158 | ] 159 | [ "HTML Input" 160 | ] 161 | , optionalAttrs 162 | textarea_ 163 | [ placeholder_ "Type your text here..." 164 | , class_ "input-area" 165 | , onInput OnInput 166 | ] (mode_ == Clear) 167 | [ value_ "" 168 | ] 169 | [] 170 | ] 171 | , div_ 172 | [ class_ "panel" ] 173 | [ div_ 174 | [ class_ "panel-header" ] 175 | [ "miso View output" 176 | ] 177 | , pre_ 178 | [ id_ "output" 179 | , class_ "output-area" 180 | ] 181 | [ text input 182 | ] 183 | ] 184 | ] 185 | ] 186 | ----------------------------------------------------------------------------- 187 | showToast :: MisoString -> JSM () 188 | showToast msg = do 189 | o <- create 190 | set @MisoString "text" msg o 191 | set @Int "duration" 3000 o 192 | toastify <- new (jsg @MisoString "Toastify") [o] 193 | void $ toastify # ("showToast" :: MisoString) $ () 194 | ----------------------------------------------------------------------------- 195 | -------------------------------------------------------------------------------- /src/Miso/From/Html.hs: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------------------------- 2 | {-# LANGUAGE LambdaCase #-} 3 | {-# LANGUAGE ViewPatterns #-} 4 | {-# LANGUAGE FlexibleContexts #-} 5 | {-# LANGUAGE OverloadedStrings #-} 6 | {-# LANGUAGE FlexibleInstances #-} 7 | {-# LANGUAGE DerivingStrategies #-} 8 | {-# LANGUAGE GeneralizedNewtypeDeriving #-} 9 | ----------------------------------------------------------------------------- 10 | module Miso.From.Html where 11 | ----------------------------------------------------------------------------- 12 | import Data.Char 13 | import Control.Monad (guard) 14 | import Control.Monad.State 15 | import Control.Applicative 16 | import Data.List hiding (takeWhile) 17 | import Data.Map (Map) 18 | import qualified Data.Map as M 19 | import Data.Maybe 20 | import Data.Text (Text) 21 | import qualified Data.Text.Lazy as LT 22 | import qualified Data.Text as T 23 | import Prelude hiding (takeWhile) 24 | import Text.HTML.Parser 25 | import Text.Pretty.Simple 26 | import Text.HTML.Tree 27 | ----------------------------------------------------------------------------- 28 | type IsOpen = Bool 29 | ----------------------------------------------------------------------------- 30 | data HTML 31 | = Node IsOpen HTMLTagName [ HTMLAttr ] [ HTML ] 32 | | TextNode Text 33 | deriving stock (Eq) 34 | ----------------------------------------------------------------------------- 35 | newtype CSS = CSS (Map Text Text) 36 | deriving stock (Eq) 37 | deriving newtype (Monoid, Semigroup) 38 | ----------------------------------------------------------------------------- 39 | instance Show CSS where 40 | show (CSS hmap) = 41 | mconcat 42 | [ "CSS.style [ " 43 | , intercalate "," (go <$> M.assocs hmap) 44 | , " ]" 45 | ] 46 | where 47 | go (k,v) = "\"" <> 48 | T.unpack (T.strip k) <> "\" =: " <> "\"" <> 49 | T.unpack (T.strip v) <> "\"" 50 | ----------------------------------------------------------------------------- 51 | data HTMLAttr = HTMLAttr Text (Maybe Text) 52 | deriving (Eq) 53 | ----------------------------------------------------------------------------- 54 | instance Show HTML where 55 | show (TextNode x) = "\"" <> T.unpack x <> "\"" 56 | show (Node isOpen t as cs) = 57 | mconcat $ 58 | [ T.unpack t 59 | , "_ " 60 | , show as 61 | ] ++ 62 | [ show cs 63 | | isOpen 64 | ] 65 | ----------------------------------------------------------------------------- 66 | upper :: Text -> Text 67 | upper (T.uncons -> Just (h,xs)) = T.cons (toUpper h) xs 68 | upper x = x 69 | ----------------------------------------------------------------------------- 70 | instance Show HTMLAttr where 71 | show (HTMLAttr "style" (Just v)) = 72 | mconcat 73 | [ T.unpack v 74 | ] 75 | show (HTMLAttr k (Just v)) 76 | | "stroke-" `T.isPrefixOf` k 77 | , Just rest <- T.stripPrefix "stroke-" k = 78 | mconcat 79 | [ " stroke" <> T.unpack (upper rest) <> "_" 80 | , " \"" 81 | , T.unpack v 82 | , "\" " 83 | ] 84 | | "data-" `T.isPrefixOf` k 85 | , Just rest <- T.stripPrefix "data-" k = 86 | mconcat 87 | [ " data_ \"" 88 | , T.unpack rest 89 | , "\"" 90 | , " \"" 91 | , T.unpack v 92 | , "\" " 93 | ] 94 | | "aria-" `T.isPrefixOf` k 95 | , Just rest <- T.stripPrefix "aria-" k = 96 | mconcat 97 | [ " aria_ \"" 98 | , T.unpack rest 99 | , "\"" 100 | , " \"" 101 | , T.unpack v 102 | , "\" " 103 | ] 104 | | T.any (=='-') k = 105 | mconcat 106 | [ " textProp \"" 107 | , T.unpack k 108 | , "\"" 109 | , " \"" 110 | , T.unpack v 111 | , "\" " 112 | ] 113 | | otherwise = 114 | mconcat 115 | [ " " 116 | , T.unpack k 117 | , "_ " 118 | , "\"" 119 | , T.unpack v 120 | , "\" " 121 | ] 122 | show (HTMLAttr x@(T.isPrefixOf "data-" -> True) Nothing) = 123 | case T.stripPrefix "data-" x of 124 | Just rest -> "data_ " <> "\"" <> T.unpack rest <> "\"" <> " \"\"" 125 | Nothing -> T.unpack x 126 | show (HTMLAttr "checked" Nothing) = 127 | "checked_ True" 128 | show (HTMLAttr k Nothing) = 129 | mconcat 130 | [ "textProp \"" 131 | , T.unpack k 132 | , "\" \"\"" 133 | ] 134 | ----------------------------------------------------------------------------- 135 | type HTMLTagName = Text 136 | ----------------------------------------------------------------------------- 137 | html :: Parser HTML 138 | html = withoutKids <|> withKids 139 | where 140 | withoutKids = 141 | textNode <|> tagSelfClose 142 | withKids = do 143 | (openName, attrs) <- tagOpen 144 | kids <- many html 145 | closeName <- tagClose 146 | guard (openName == closeName) 147 | pure (Node True openName attrs kids) 148 | ----------------------------------------------------------------------------- 149 | tagOpen :: Parser (TagName, [HTMLAttr]) 150 | tagOpen = do 151 | TagOpen openName attrs <- 152 | satisfy $ \case 153 | TagOpen{} -> True 154 | _ -> False 155 | let htmlAttrs = 156 | [ processStyle (HTMLAttr attrName value) 157 | | Attr attrName attrValue <- attrs 158 | , let value 159 | | T.null attrValue = Nothing 160 | | otherwise = Just attrValue 161 | ] 162 | pure (openName, htmlAttrs) 163 | ----------------------------------------------------------------------------- 164 | tagClose :: Parser TagName 165 | tagClose = do 166 | TagClose closeName <- 167 | satisfy $ \case 168 | TagClose{} -> True 169 | _ -> False 170 | pure closeName 171 | ----------------------------------------------------------------------------- 172 | tagSelfClose :: Parser HTML 173 | tagSelfClose = do 174 | TagSelfClose name attrs <- 175 | satisfy $ \case 176 | TagSelfClose {} -> True 177 | _ -> False 178 | let htmlAttrs = 179 | [ processStyle (HTMLAttr attrName value) 180 | | Attr attrName attrValue <- attrs 181 | , let value 182 | | T.null attrValue = Nothing 183 | | otherwise = Just attrValue 184 | ] 185 | pure (Node False name htmlAttrs []) 186 | ----------------------------------------------------------------------------- 187 | textNode :: Parser HTML 188 | textNode = leaf <|> leafChar 189 | where 190 | leaf :: Parser HTML 191 | leaf = do 192 | ContentText txt <- 193 | satisfy $ \case 194 | ContentText {} -> True 195 | _ -> False 196 | pure (TextNode txt) 197 | 198 | leafChar :: Parser HTML 199 | leafChar = do 200 | ContentChar t <- 201 | satisfy $ \case 202 | ContentChar {} -> True 203 | _ -> False 204 | pure (TextNode (T.singleton t)) 205 | ------------------------------------------------------------------------------ 206 | processStyle :: HTMLAttr -> HTMLAttr 207 | processStyle (HTMLAttr "style" (Just cssText)) = 208 | HTMLAttr "style" $ Just (T.pack (show parsedCss)) 209 | where 210 | parsedCss :: CSS 211 | parsedCss = CSS cssMap 212 | where 213 | cssMap 214 | = M.fromList 215 | [ (k,v) 216 | | [k,v] <- T.splitOn ":" <$> T.splitOn ";" cssText 217 | ] 218 | processStyle attr = attr 219 | ------------------------------------------------------------------------------ 220 | isComment :: Token -> Bool 221 | isComment Comment {} = True 222 | isComment _ = False 223 | ----------------------------------------------------------------------------- 224 | isDoctype :: Token -> Bool 225 | isDoctype Doctype {} = True 226 | isDoctype _ = False 227 | ----------------------------------------------------------------------------- 228 | isEmptyTextNode :: Token -> Bool 229 | isEmptyTextNode (ContentText txt) 230 | = T.null 231 | $ T.filter (`notElem` ['\n', '\t', ' ']) 232 | $ txt 233 | isEmptyTextNode _ = False 234 | ----------------------------------------------------------------------------- 235 | getTokens :: Text -> [Token] 236 | getTokens input = preprocess $ 237 | let 238 | tokens = parseTokens input 239 | in 240 | [ case t of 241 | ContentText txt -> 242 | ContentText (T.strip txt) 243 | _ -> t 244 | | t <- tokens 245 | , not (isComment t) 246 | && not (isDoctype t) 247 | && not (isEmptyTextNode t) 248 | ] 249 | ----------------------------------------------------------------------------- 250 | process :: Text -> Text 251 | process input = 252 | case parse html (getTokens input) of 253 | Right r -> 254 | T.pack (show r) 255 | Left e -> 256 | T.pack (show e) 257 | ----------------------------------------------------------------------------- 258 | preprocess :: [Token] -> [Token] 259 | preprocess = fmap go 260 | where 261 | go (TagOpen name attrs) 262 | | name `elem` nonClosing = TagSelfClose name attrs 263 | | otherwise = TagOpen name attrs 264 | go x = x 265 | ----------------------------------------------------------------------------- 266 | processPretty :: Text -> Text 267 | processPretty input = 268 | case parse html (getTokens input) of 269 | Right r -> 270 | LT.toStrict (pShow r) 271 | Left e -> 272 | LT.toStrict (pShow e) 273 | ----------------------------------------------------------------------------- 274 | data ParseError a 275 | = UnexpectedParse [Token] 276 | | Ambiguous [(a, [Token])] 277 | | NoParses Token 278 | | EmptyStream 279 | deriving (Show, Eq) 280 | ----------------------------------------------------------------------------- 281 | parse :: Parser a -> [Token] -> Either (ParseError a) a 282 | parse _ [] = Left EmptyStream 283 | parse parser tokens = 284 | case runStateT parser tokens of 285 | [] -> Left (NoParses (last tokens)) 286 | [(x, [])] -> Right x 287 | [(_, xs)] -> Left (UnexpectedParse xs) 288 | xs -> Left (Ambiguous xs) 289 | ----------------------------------------------------------------------------- 290 | type Parser a = StateT [Token] [] a 291 | ----------------------------------------------------------------------------- 292 | satisfy :: (Token -> Bool) -> Parser Token 293 | satisfy f = StateT $ \tokens -> 294 | case tokens of 295 | t : ts | f t -> [(t, ts)] 296 | _ -> [] 297 | ----------------------------------------------------------------------------- 298 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "HTTP": { 4 | "flake": false, 5 | "locked": { 6 | "lastModified": 1451647621, 7 | "narHash": "sha256-oHIyw3x0iKBexEo49YeUDV1k74ZtyYKGR2gNJXXRxts=", 8 | "owner": "phadej", 9 | "repo": "HTTP", 10 | "rev": "9bc0996d412fef1787449d841277ef663ad9a915", 11 | "type": "github" 12 | }, 13 | "original": { 14 | "owner": "phadej", 15 | "repo": "HTTP", 16 | "type": "github" 17 | } 18 | }, 19 | "cabal-32": { 20 | "flake": false, 21 | "locked": { 22 | "lastModified": 1603716527, 23 | "narHash": "sha256-X0TFfdD4KZpwl0Zr6x+PLxUt/VyKQfX7ylXHdmZIL+w=", 24 | "owner": "haskell", 25 | "repo": "cabal", 26 | "rev": "48bf10787e27364730dd37a42b603cee8d6af7ee", 27 | "type": "github" 28 | }, 29 | "original": { 30 | "owner": "haskell", 31 | "ref": "3.2", 32 | "repo": "cabal", 33 | "type": "github" 34 | } 35 | }, 36 | "cabal-34": { 37 | "flake": false, 38 | "locked": { 39 | "lastModified": 1645834128, 40 | "narHash": "sha256-wG3d+dOt14z8+ydz4SL7pwGfe7SiimxcD/LOuPCV6xM=", 41 | "owner": "haskell", 42 | "repo": "cabal", 43 | "rev": "5ff598c67f53f7c4f48e31d722ba37172230c462", 44 | "type": "github" 45 | }, 46 | "original": { 47 | "owner": "haskell", 48 | "ref": "3.4", 49 | "repo": "cabal", 50 | "type": "github" 51 | } 52 | }, 53 | "cabal-36": { 54 | "flake": false, 55 | "locked": { 56 | "lastModified": 1669081697, 57 | "narHash": "sha256-I5or+V7LZvMxfbYgZATU4awzkicBwwok4mVoje+sGmU=", 58 | "owner": "haskell", 59 | "repo": "cabal", 60 | "rev": "8fd619e33d34924a94e691c5fea2c42f0fc7f144", 61 | "type": "github" 62 | }, 63 | "original": { 64 | "owner": "haskell", 65 | "ref": "3.6", 66 | "repo": "cabal", 67 | "type": "github" 68 | } 69 | }, 70 | "cardano-shell": { 71 | "flake": false, 72 | "locked": { 73 | "lastModified": 1608537748, 74 | "narHash": "sha256-PulY1GfiMgKVnBci3ex4ptk2UNYMXqGjJOxcPy2KYT4=", 75 | "owner": "input-output-hk", 76 | "repo": "cardano-shell", 77 | "rev": "9392c75087cb9a3d453998f4230930dea3a95725", 78 | "type": "github" 79 | }, 80 | "original": { 81 | "owner": "input-output-hk", 82 | "repo": "cardano-shell", 83 | "type": "github" 84 | } 85 | }, 86 | "flake-compat": { 87 | "flake": false, 88 | "locked": { 89 | "lastModified": 1672831974, 90 | "narHash": "sha256-z9k3MfslLjWQfnjBtEtJZdq3H7kyi2kQtUThfTgdRk0=", 91 | "owner": "input-output-hk", 92 | "repo": "flake-compat", 93 | "rev": "45f2638735f8cdc40fe302742b79f248d23eb368", 94 | "type": "github" 95 | }, 96 | "original": { 97 | "owner": "input-output-hk", 98 | "ref": "hkm/gitlab-fix", 99 | "repo": "flake-compat", 100 | "type": "github" 101 | } 102 | }, 103 | "flake-utils": { 104 | "inputs": { 105 | "systems": "systems" 106 | }, 107 | "locked": { 108 | "lastModified": 1731533236, 109 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 110 | "owner": "numtide", 111 | "repo": "flake-utils", 112 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 113 | "type": "github" 114 | }, 115 | "original": { 116 | "owner": "numtide", 117 | "repo": "flake-utils", 118 | "type": "github" 119 | } 120 | }, 121 | "flake-utils_2": { 122 | "inputs": { 123 | "systems": "systems_2" 124 | }, 125 | "locked": { 126 | "lastModified": 1731533236, 127 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 128 | "owner": "numtide", 129 | "repo": "flake-utils", 130 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 131 | "type": "github" 132 | }, 133 | "original": { 134 | "owner": "numtide", 135 | "repo": "flake-utils", 136 | "type": "github" 137 | } 138 | }, 139 | "flake-utils_3": { 140 | "inputs": { 141 | "systems": "systems_3" 142 | }, 143 | "locked": { 144 | "lastModified": 1731533236, 145 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 146 | "owner": "numtide", 147 | "repo": "flake-utils", 148 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 149 | "type": "github" 150 | }, 151 | "original": { 152 | "owner": "numtide", 153 | "repo": "flake-utils", 154 | "type": "github" 155 | } 156 | }, 157 | "flake-utils_4": { 158 | "inputs": { 159 | "systems": "systems_4" 160 | }, 161 | "locked": { 162 | "lastModified": 1726560853, 163 | "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=", 164 | "owner": "numtide", 165 | "repo": "flake-utils", 166 | "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a", 167 | "type": "github" 168 | }, 169 | "original": { 170 | "owner": "numtide", 171 | "repo": "flake-utils", 172 | "type": "github" 173 | } 174 | }, 175 | "ghc-8.6.5-iohk": { 176 | "flake": false, 177 | "locked": { 178 | "lastModified": 1600920045, 179 | "narHash": "sha256-DO6kxJz248djebZLpSzTGD6s8WRpNI9BTwUeOf5RwY8=", 180 | "owner": "input-output-hk", 181 | "repo": "ghc", 182 | "rev": "95713a6ecce4551240da7c96b6176f980af75cae", 183 | "type": "github" 184 | }, 185 | "original": { 186 | "owner": "input-output-hk", 187 | "ref": "release/8.6.5-iohk", 188 | "repo": "ghc", 189 | "type": "github" 190 | } 191 | }, 192 | "ghc-wasm-meta": { 193 | "inputs": { 194 | "flake-utils": "flake-utils_2", 195 | "nixpkgs": "nixpkgs" 196 | }, 197 | "locked": { 198 | "host": "gitlab.haskell.org", 199 | "lastModified": 1756352258, 200 | "narHash": "sha256-p1aF+DKKRuG5BAYbl4yslNvcj2EZFtdRvYSgs8CrtpE=", 201 | "owner": "haskell-wasm", 202 | "repo": "ghc-wasm-meta", 203 | "rev": "8e5d32a6bad7968e267dad4f0786c83cb327a7d9", 204 | "type": "gitlab" 205 | }, 206 | "original": { 207 | "host": "gitlab.haskell.org", 208 | "owner": "haskell-wasm", 209 | "repo": "ghc-wasm-meta", 210 | "type": "gitlab" 211 | } 212 | }, 213 | "hackage": { 214 | "flake": false, 215 | "locked": { 216 | "lastModified": 1745454330, 217 | "narHash": "sha256-MA9xYIHwc1JcffoUx1toBCpcmmx1MYqi4Ds9n+iP8Ig=", 218 | "owner": "input-output-hk", 219 | "repo": "hackage.nix", 220 | "rev": "989ae6c63d1f2fcee69aa7f126010ac5844e1637", 221 | "type": "github" 222 | }, 223 | "original": { 224 | "owner": "input-output-hk", 225 | "repo": "hackage.nix", 226 | "type": "github" 227 | } 228 | }, 229 | "hackage-for-stackage": { 230 | "flake": false, 231 | "locked": { 232 | "lastModified": 1745454319, 233 | "narHash": "sha256-SCBdlrFg1TmVqrrM6UWLuE+dhfDV5cKrNgdFTaR91gE=", 234 | "owner": "input-output-hk", 235 | "repo": "hackage.nix", 236 | "rev": "cfa745733399e92f1214d94e26e22f9f721702ba", 237 | "type": "github" 238 | }, 239 | "original": { 240 | "owner": "input-output-hk", 241 | "ref": "for-stackage", 242 | "repo": "hackage.nix", 243 | "type": "github" 244 | } 245 | }, 246 | "haskell-ci": { 247 | "flake": false, 248 | "locked": { 249 | "lastModified": 1743351534, 250 | "narHash": "sha256-oowOok6+RLk7n6vHWwYufxyUmUpun/VMo8hXpfm1+d8=", 251 | "owner": "haskell-CI", 252 | "repo": "haskell-ci", 253 | "rev": "f0fd898ab14070fa46e9fd542a2b487a8146d88e", 254 | "type": "github" 255 | }, 256 | "original": { 257 | "owner": "haskell-CI", 258 | "repo": "haskell-ci", 259 | "type": "github" 260 | } 261 | }, 262 | "haskellNix": { 263 | "inputs": { 264 | "HTTP": "HTTP", 265 | "cabal-32": "cabal-32", 266 | "cabal-34": "cabal-34", 267 | "cabal-36": "cabal-36", 268 | "cardano-shell": "cardano-shell", 269 | "flake-compat": "flake-compat", 270 | "ghc-8.6.5-iohk": "ghc-8.6.5-iohk", 271 | "hackage": "hackage", 272 | "hackage-for-stackage": "hackage-for-stackage", 273 | "hls": "hls", 274 | "hls-1.10": "hls-1.10", 275 | "hls-2.0": "hls-2.0", 276 | "hls-2.10": "hls-2.10", 277 | "hls-2.2": "hls-2.2", 278 | "hls-2.3": "hls-2.3", 279 | "hls-2.4": "hls-2.4", 280 | "hls-2.5": "hls-2.5", 281 | "hls-2.6": "hls-2.6", 282 | "hls-2.7": "hls-2.7", 283 | "hls-2.8": "hls-2.8", 284 | "hls-2.9": "hls-2.9", 285 | "hpc-coveralls": "hpc-coveralls", 286 | "iserv-proxy": "iserv-proxy", 287 | "nixpkgs": [ 288 | "miso", 289 | "jsaddle", 290 | "haskellNix", 291 | "nixpkgs-unstable" 292 | ], 293 | "nixpkgs-2305": "nixpkgs-2305", 294 | "nixpkgs-2311": "nixpkgs-2311", 295 | "nixpkgs-2405": "nixpkgs-2405", 296 | "nixpkgs-2411": "nixpkgs-2411", 297 | "nixpkgs-unstable": "nixpkgs-unstable", 298 | "old-ghc-nix": "old-ghc-nix", 299 | "stackage": "stackage" 300 | }, 301 | "locked": { 302 | "lastModified": 1745455900, 303 | "narHash": "sha256-H2EyIfyi9PsTARBzsjwfyPgniFgPOQLAX8nkrvKMQOU=", 304 | "owner": "input-output-hk", 305 | "repo": "haskell.nix", 306 | "rev": "cafa5223a5411fa7545f21d76e9b8743f4d00c29", 307 | "type": "github" 308 | }, 309 | "original": { 310 | "owner": "input-output-hk", 311 | "repo": "haskell.nix", 312 | "type": "github" 313 | } 314 | }, 315 | "hls": { 316 | "flake": false, 317 | "locked": { 318 | "lastModified": 1741604408, 319 | "narHash": "sha256-tuq3+Ip70yu89GswZ7DSINBpwRprnWnl6xDYnS4GOsc=", 320 | "owner": "haskell", 321 | "repo": "haskell-language-server", 322 | "rev": "682d6894c94087da5e566771f25311c47e145359", 323 | "type": "github" 324 | }, 325 | "original": { 326 | "owner": "haskell", 327 | "repo": "haskell-language-server", 328 | "type": "github" 329 | } 330 | }, 331 | "hls-1.10": { 332 | "flake": false, 333 | "locked": { 334 | "lastModified": 1680000865, 335 | "narHash": "sha256-rc7iiUAcrHxwRM/s0ErEsSPxOR3u8t7DvFeWlMycWgo=", 336 | "owner": "haskell", 337 | "repo": "haskell-language-server", 338 | "rev": "b08691db779f7a35ff322b71e72a12f6e3376fd9", 339 | "type": "github" 340 | }, 341 | "original": { 342 | "owner": "haskell", 343 | "ref": "1.10.0.0", 344 | "repo": "haskell-language-server", 345 | "type": "github" 346 | } 347 | }, 348 | "hls-2.0": { 349 | "flake": false, 350 | "locked": { 351 | "lastModified": 1687698105, 352 | "narHash": "sha256-OHXlgRzs/kuJH8q7Sxh507H+0Rb8b7VOiPAjcY9sM1k=", 353 | "owner": "haskell", 354 | "repo": "haskell-language-server", 355 | "rev": "783905f211ac63edf982dd1889c671653327e441", 356 | "type": "github" 357 | }, 358 | "original": { 359 | "owner": "haskell", 360 | "ref": "2.0.0.1", 361 | "repo": "haskell-language-server", 362 | "type": "github" 363 | } 364 | }, 365 | "hls-2.10": { 366 | "flake": false, 367 | "locked": { 368 | "lastModified": 1743069404, 369 | "narHash": "sha256-q4kDFyJDDeoGqfEtrZRx4iqMVEC2MOzCToWsFY+TOzY=", 370 | "owner": "haskell", 371 | "repo": "haskell-language-server", 372 | "rev": "2318c61db3a01e03700bd4b05665662929b7fe8b", 373 | "type": "github" 374 | }, 375 | "original": { 376 | "owner": "haskell", 377 | "ref": "2.10.0.0", 378 | "repo": "haskell-language-server", 379 | "type": "github" 380 | } 381 | }, 382 | "hls-2.2": { 383 | "flake": false, 384 | "locked": { 385 | "lastModified": 1693064058, 386 | "narHash": "sha256-8DGIyz5GjuCFmohY6Fa79hHA/p1iIqubfJUTGQElbNk=", 387 | "owner": "haskell", 388 | "repo": "haskell-language-server", 389 | "rev": "b30f4b6cf5822f3112c35d14a0cba51f3fe23b85", 390 | "type": "github" 391 | }, 392 | "original": { 393 | "owner": "haskell", 394 | "ref": "2.2.0.0", 395 | "repo": "haskell-language-server", 396 | "type": "github" 397 | } 398 | }, 399 | "hls-2.3": { 400 | "flake": false, 401 | "locked": { 402 | "lastModified": 1695910642, 403 | "narHash": "sha256-tR58doOs3DncFehHwCLczJgntyG/zlsSd7DgDgMPOkI=", 404 | "owner": "haskell", 405 | "repo": "haskell-language-server", 406 | "rev": "458ccdb55c9ea22cd5d13ec3051aaefb295321be", 407 | "type": "github" 408 | }, 409 | "original": { 410 | "owner": "haskell", 411 | "ref": "2.3.0.0", 412 | "repo": "haskell-language-server", 413 | "type": "github" 414 | } 415 | }, 416 | "hls-2.4": { 417 | "flake": false, 418 | "locked": { 419 | "lastModified": 1699862708, 420 | "narHash": "sha256-YHXSkdz53zd0fYGIYOgLt6HrA0eaRJi9mXVqDgmvrjk=", 421 | "owner": "haskell", 422 | "repo": "haskell-language-server", 423 | "rev": "54507ef7e85fa8e9d0eb9a669832a3287ffccd57", 424 | "type": "github" 425 | }, 426 | "original": { 427 | "owner": "haskell", 428 | "ref": "2.4.0.1", 429 | "repo": "haskell-language-server", 430 | "type": "github" 431 | } 432 | }, 433 | "hls-2.5": { 434 | "flake": false, 435 | "locked": { 436 | "lastModified": 1701080174, 437 | "narHash": "sha256-fyiR9TaHGJIIR0UmcCb73Xv9TJq3ht2ioxQ2mT7kVdc=", 438 | "owner": "haskell", 439 | "repo": "haskell-language-server", 440 | "rev": "27f8c3d3892e38edaef5bea3870161815c4d014c", 441 | "type": "github" 442 | }, 443 | "original": { 444 | "owner": "haskell", 445 | "ref": "2.5.0.0", 446 | "repo": "haskell-language-server", 447 | "type": "github" 448 | } 449 | }, 450 | "hls-2.6": { 451 | "flake": false, 452 | "locked": { 453 | "lastModified": 1705325287, 454 | "narHash": "sha256-+P87oLdlPyMw8Mgoul7HMWdEvWP/fNlo8jyNtwME8E8=", 455 | "owner": "haskell", 456 | "repo": "haskell-language-server", 457 | "rev": "6e0b342fa0327e628610f2711f8c3e4eaaa08b1e", 458 | "type": "github" 459 | }, 460 | "original": { 461 | "owner": "haskell", 462 | "ref": "2.6.0.0", 463 | "repo": "haskell-language-server", 464 | "type": "github" 465 | } 466 | }, 467 | "hls-2.7": { 468 | "flake": false, 469 | "locked": { 470 | "lastModified": 1708965829, 471 | "narHash": "sha256-LfJ+TBcBFq/XKoiNI7pc4VoHg4WmuzsFxYJ3Fu+Jf+M=", 472 | "owner": "haskell", 473 | "repo": "haskell-language-server", 474 | "rev": "50322b0a4aefb27adc5ec42f5055aaa8f8e38001", 475 | "type": "github" 476 | }, 477 | "original": { 478 | "owner": "haskell", 479 | "ref": "2.7.0.0", 480 | "repo": "haskell-language-server", 481 | "type": "github" 482 | } 483 | }, 484 | "hls-2.8": { 485 | "flake": false, 486 | "locked": { 487 | "lastModified": 1715153580, 488 | "narHash": "sha256-Vi/iUt2pWyUJlo9VrYgTcbRviWE0cFO6rmGi9rmALw0=", 489 | "owner": "haskell", 490 | "repo": "haskell-language-server", 491 | "rev": "dd1be1beb16700de59e0d6801957290bcf956a0a", 492 | "type": "github" 493 | }, 494 | "original": { 495 | "owner": "haskell", 496 | "ref": "2.8.0.0", 497 | "repo": "haskell-language-server", 498 | "type": "github" 499 | } 500 | }, 501 | "hls-2.9": { 502 | "flake": false, 503 | "locked": { 504 | "lastModified": 1719993701, 505 | "narHash": "sha256-wy348++MiMm/xwtI9M3vVpqj2qfGgnDcZIGXw8sF1sA=", 506 | "owner": "haskell", 507 | "repo": "haskell-language-server", 508 | "rev": "90319a7e62ab93ab65a95f8f2bcf537e34dae76a", 509 | "type": "github" 510 | }, 511 | "original": { 512 | "owner": "haskell", 513 | "ref": "2.9.0.1", 514 | "repo": "haskell-language-server", 515 | "type": "github" 516 | } 517 | }, 518 | "hpc-coveralls": { 519 | "flake": false, 520 | "locked": { 521 | "lastModified": 1607498076, 522 | "narHash": "sha256-8uqsEtivphgZWYeUo5RDUhp6bO9j2vaaProQxHBltQk=", 523 | "owner": "sevanspowell", 524 | "repo": "hpc-coveralls", 525 | "rev": "14df0f7d229f4cd2e79f8eabb1a740097fdfa430", 526 | "type": "github" 527 | }, 528 | "original": { 529 | "owner": "sevanspowell", 530 | "repo": "hpc-coveralls", 531 | "type": "github" 532 | } 533 | }, 534 | "iserv-proxy": { 535 | "flake": false, 536 | "locked": { 537 | "lastModified": 1742121966, 538 | "narHash": "sha256-x4bg4OoKAPnayom0nWc0BmlxgRMMHk6lEPvbiyFBq1s=", 539 | "owner": "stable-haskell", 540 | "repo": "iserv-proxy", 541 | "rev": "e9dc86ed6ad71f0368c16672081c8f26406c3a7e", 542 | "type": "github" 543 | }, 544 | "original": { 545 | "owner": "stable-haskell", 546 | "ref": "iserv-syms", 547 | "repo": "iserv-proxy", 548 | "type": "github" 549 | } 550 | }, 551 | "jsaddle": { 552 | "inputs": { 553 | "flake-utils": "flake-utils_3", 554 | "haskell-ci": "haskell-ci", 555 | "haskellNix": "haskellNix", 556 | "nixpkgs": [ 557 | "miso", 558 | "jsaddle", 559 | "haskellNix", 560 | "nixpkgs-unstable" 561 | ] 562 | }, 563 | "locked": { 564 | "lastModified": 1756701389, 565 | "narHash": "sha256-WUXl+5QfhsZKf6V+h0qhl6Jgy6SzR3HzMQsfbWL0jkE=", 566 | "owner": "ghcjs", 567 | "repo": "jsaddle", 568 | "rev": "0fb7260ad02592546c9f180078d770256fb1f0f6", 569 | "type": "github" 570 | }, 571 | "original": { 572 | "owner": "ghcjs", 573 | "repo": "jsaddle", 574 | "rev": "0fb7260ad02592546c9f180078d770256fb1f0f6", 575 | "type": "github" 576 | } 577 | }, 578 | "miso": { 579 | "inputs": { 580 | "flake-utils": "flake-utils", 581 | "ghc-wasm-meta": "ghc-wasm-meta", 582 | "jsaddle": "jsaddle", 583 | "nixpkgs": "nixpkgs_2", 584 | "servant": "servant" 585 | }, 586 | "locked": { 587 | "lastModified": 1757169826, 588 | "narHash": "sha256-j3pT8FqSvO0jPeW/CTM8QMwUysS56HG542L6Y2RnRJQ=", 589 | "owner": "dmjio", 590 | "repo": "miso", 591 | "rev": "1afbc049691d2cce9e4c8c6d71760055d45c072b", 592 | "type": "github" 593 | }, 594 | "original": { 595 | "owner": "dmjio", 596 | "repo": "miso", 597 | "type": "github" 598 | } 599 | }, 600 | "nixpkgs": { 601 | "locked": { 602 | "lastModified": 1755767206, 603 | "narHash": "sha256-yi+50PemAF64H5sA4Bl3RYzz3Yniw0538SPLl3DxGU0=", 604 | "owner": "NixOS", 605 | "repo": "nixpkgs", 606 | "rev": "f90bda01c396f058bfe42d0cecb4ba776160a953", 607 | "type": "github" 608 | }, 609 | "original": { 610 | "owner": "NixOS", 611 | "ref": "nixos-25.05", 612 | "repo": "nixpkgs", 613 | "type": "github" 614 | } 615 | }, 616 | "nixpkgs-2305": { 617 | "locked": { 618 | "lastModified": 1705033721, 619 | "narHash": "sha256-K5eJHmL1/kev6WuqyqqbS1cdNnSidIZ3jeqJ7GbrYnQ=", 620 | "owner": "NixOS", 621 | "repo": "nixpkgs", 622 | "rev": "a1982c92d8980a0114372973cbdfe0a307f1bdea", 623 | "type": "github" 624 | }, 625 | "original": { 626 | "owner": "NixOS", 627 | "ref": "nixpkgs-23.05-darwin", 628 | "repo": "nixpkgs", 629 | "type": "github" 630 | } 631 | }, 632 | "nixpkgs-2311": { 633 | "locked": { 634 | "lastModified": 1719957072, 635 | "narHash": "sha256-gvFhEf5nszouwLAkT9nWsDzocUTqLWHuL++dvNjMp9I=", 636 | "owner": "NixOS", 637 | "repo": "nixpkgs", 638 | "rev": "7144d6241f02d171d25fba3edeaf15e0f2592105", 639 | "type": "github" 640 | }, 641 | "original": { 642 | "owner": "NixOS", 643 | "ref": "nixpkgs-23.11-darwin", 644 | "repo": "nixpkgs", 645 | "type": "github" 646 | } 647 | }, 648 | "nixpkgs-2405": { 649 | "locked": { 650 | "lastModified": 1735564410, 651 | "narHash": "sha256-HB/FA0+1gpSs8+/boEavrGJH+Eq08/R2wWNph1sM1Dg=", 652 | "owner": "NixOS", 653 | "repo": "nixpkgs", 654 | "rev": "1e7a8f391f1a490460760065fa0630b5520f9cf8", 655 | "type": "github" 656 | }, 657 | "original": { 658 | "owner": "NixOS", 659 | "ref": "nixpkgs-24.05-darwin", 660 | "repo": "nixpkgs", 661 | "type": "github" 662 | } 663 | }, 664 | "nixpkgs-2411": { 665 | "locked": { 666 | "lastModified": 1739151041, 667 | "narHash": "sha256-uNszcul7y++oBiyYXjHEDw/AHeLNp8B6pyWOB+RLA/4=", 668 | "owner": "NixOS", 669 | "repo": "nixpkgs", 670 | "rev": "94792ab2a6beaec81424445bf917ca2556fbeade", 671 | "type": "github" 672 | }, 673 | "original": { 674 | "owner": "NixOS", 675 | "ref": "nixpkgs-24.11-darwin", 676 | "repo": "nixpkgs", 677 | "type": "github" 678 | } 679 | }, 680 | "nixpkgs-unstable": { 681 | "locked": { 682 | "lastModified": 1737110817, 683 | "narHash": "sha256-DSenga8XjPaUV5KUFW/i3rNkN7jm9XmguW+qQ1ZJTR4=", 684 | "owner": "NixOS", 685 | "repo": "nixpkgs", 686 | "rev": "041c867bad68dfe34b78b2813028a2e2ea70a23c", 687 | "type": "github" 688 | }, 689 | "original": { 690 | "owner": "NixOS", 691 | "ref": "nixpkgs-unstable", 692 | "repo": "nixpkgs", 693 | "type": "github" 694 | } 695 | }, 696 | "nixpkgs_2": { 697 | "locked": { 698 | "lastModified": 1751996040, 699 | "narHash": "sha256-DOjNE+DYZ/YZo1UkXcJNlvSKEBowWATX6o4s0WuAzuA=", 700 | "owner": "nixos", 701 | "repo": "nixpkgs", 702 | "rev": "9e2e8a7878573d312db421d69e071690ec34e98c", 703 | "type": "github" 704 | }, 705 | "original": { 706 | "owner": "nixos", 707 | "repo": "nixpkgs", 708 | "rev": "9e2e8a7878573d312db421d69e071690ec34e98c", 709 | "type": "github" 710 | } 711 | }, 712 | "nixpkgs_3": { 713 | "locked": { 714 | "lastModified": 1728940272, 715 | "narHash": "sha256-zVl25LPDCt1l34AS7Ba4MPTxHQ8tkFL2hxVGEntmngI=", 716 | "owner": "NixOS", 717 | "repo": "nixpkgs", 718 | "rev": "8eec6bbcf05c919b19ce8dfb3f96cc4585d30cce", 719 | "type": "github" 720 | }, 721 | "original": { 722 | "owner": "NixOS", 723 | "ref": "nixpkgs-24.05-darwin", 724 | "repo": "nixpkgs", 725 | "type": "github" 726 | } 727 | }, 728 | "old-ghc-nix": { 729 | "flake": false, 730 | "locked": { 731 | "lastModified": 1631092763, 732 | "narHash": "sha256-sIKgO+z7tj4lw3u6oBZxqIhDrzSkvpHtv0Kki+lh9Fg=", 733 | "owner": "angerman", 734 | "repo": "old-ghc-nix", 735 | "rev": "af48a7a7353e418119b6dfe3cd1463a657f342b8", 736 | "type": "github" 737 | }, 738 | "original": { 739 | "owner": "angerman", 740 | "ref": "master", 741 | "repo": "old-ghc-nix", 742 | "type": "github" 743 | } 744 | }, 745 | "root": { 746 | "inputs": { 747 | "miso": "miso" 748 | } 749 | }, 750 | "servant": { 751 | "inputs": { 752 | "flake-utils": "flake-utils_4", 753 | "nixpkgs": "nixpkgs_3" 754 | }, 755 | "locked": { 756 | "lastModified": 1747753492, 757 | "narHash": "sha256-zWlU6/7MU0J/amOSZHEgVltMN9K4luNK1JV6irM9ozM=", 758 | "owner": "haskell-servant", 759 | "repo": "servant", 760 | "rev": "e07e92abd62641fc0f199a33e5131de273140cb0", 761 | "type": "github" 762 | }, 763 | "original": { 764 | "owner": "haskell-servant", 765 | "repo": "servant", 766 | "rev": "e07e92abd62641fc0f199a33e5131de273140cb0", 767 | "type": "github" 768 | } 769 | }, 770 | "stackage": { 771 | "flake": false, 772 | "locked": { 773 | "lastModified": 1745453555, 774 | "narHash": "sha256-UdWBshU4hyz5Q76yqxvkhbc+ywAYeQtrigyUnOGTaV4=", 775 | "owner": "input-output-hk", 776 | "repo": "stackage.nix", 777 | "rev": "077ab84d76fdcd96ba879b135f35c1edb853fcd2", 778 | "type": "github" 779 | }, 780 | "original": { 781 | "owner": "input-output-hk", 782 | "repo": "stackage.nix", 783 | "type": "github" 784 | } 785 | }, 786 | "systems": { 787 | "locked": { 788 | "lastModified": 1681028828, 789 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 790 | "owner": "nix-systems", 791 | "repo": "default", 792 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 793 | "type": "github" 794 | }, 795 | "original": { 796 | "owner": "nix-systems", 797 | "repo": "default", 798 | "type": "github" 799 | } 800 | }, 801 | "systems_2": { 802 | "locked": { 803 | "lastModified": 1681028828, 804 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 805 | "owner": "nix-systems", 806 | "repo": "default", 807 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 808 | "type": "github" 809 | }, 810 | "original": { 811 | "owner": "nix-systems", 812 | "repo": "default", 813 | "type": "github" 814 | } 815 | }, 816 | "systems_3": { 817 | "locked": { 818 | "lastModified": 1681028828, 819 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 820 | "owner": "nix-systems", 821 | "repo": "default", 822 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 823 | "type": "github" 824 | }, 825 | "original": { 826 | "owner": "nix-systems", 827 | "repo": "default", 828 | "type": "github" 829 | } 830 | }, 831 | "systems_4": { 832 | "locked": { 833 | "lastModified": 1681028828, 834 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 835 | "owner": "nix-systems", 836 | "repo": "default", 837 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 838 | "type": "github" 839 | }, 840 | "original": { 841 | "owner": "nix-systems", 842 | "repo": "default", 843 | "type": "github" 844 | } 845 | } 846 | }, 847 | "root": "root", 848 | "version": 7 849 | } 850 | --------------------------------------------------------------------------------