packages feed

web-view-colonnade (empty) → 0.1.0.0

raw patch · 6 files changed

+832/−0 lines, 6 filesdep +basedep +colonnadedep +containers

Dependencies added: base, colonnade, containers, text, vector, web-view, web-view-colonnade

Files

+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2025 José Lorenzo Rodríguez++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ README.md view
@@ -0,0 +1,142 @@+# Web View Colonnade++Build HTML tables using [colonnade](https://hackage.haskell.org/package/colonnade) and rendering with the [web-view](https://hackage.haskell.org/package/web-view) library in Haskell.++This library provides functionality similar to `lucid-colonnade` and `blaze-colonnade`, but for the web-view library. It lets you build complex HTML tables with minimal boilerplate.++## Installation++Add the following to your `package.yaml` or `.cabal` file:++```yaml+dependencies:+  - colonnade+  - web-view+  - web-view-colonnade+```++## Examples++### Basic Example++```haskell+{-# LANGUAGE OverloadedStrings #-}++import qualified Data.Text as T+import qualified Colonnade as C+import qualified Web.View.View as V+import qualified Web.View.Element as E+import Web.View (renderText)+import WebView.Colonnade++-- Define a data type to represent our data+data Person = Person+  { name :: T.Text+  , age :: Int+  }++-- Define some data+people :: [Person]+people =+  [ Person "Alice" 30+  , Person "Bob" 25+  , Person "Carol" 35+  ]++-- Define the table structure+personTable :: C.Colonnade C.Headed Person (V.View c ())+personTable = mconcat+  [ C.headed "Name" (E.text . name)+  , C.headed "Age" (E.text . T.pack . show . age)+  ]++main :: IO ()+main = do+  -- Render a table with custom attributes+  let html = encodeHtmlTable (V.extClass "person-table") personTable people+  putStrLn $ renderText html+```++This produces:++```html+<table class='person-table'>+  <thead>+    <tr>+      <th>Name</th>+      <th>Age</th>+    </tr>+  </thead>+  <tbody>+    <tr>+      <td>Alice</td>+      <td>30</td>+    </tr>+    <tr>+      <td>Bob</td>+      <td>25</td>+    </tr>+    <tr>+      <td>Carol</td>+      <td>35</td>+    </tr>+  </tbody>+</table>+```++### Cell Attributes++The `Cell` type allows you to add attributes to individual table cells:++```haskell+personCellTable :: C.Colonnade C.Headed Person (Cell c)+personCellTable = mconcat+  [ C.headed "Name" (\p -> Cell (V.extClass "name-cell") (E.text $ name p))+  , C.headed "Age" (\p -> Cell (V.extClass "age-cell") (E.text . T.pack . show $ age p))+  ]++main :: IO ()+main = do+  let html = encodeCellTable (V.extClass "person-table") personCellTable people+  putStrLn $ renderText html+```++This will render each cell with the appropriate class attributes.++## Contributing++We welcome contributions to web-view-colonnade! This project uses a nix-based devenv shell for development.++### Setting Up the Development Environment++1. Make sure you have [Nix](https://nixos.org/download.html) installed+2. Install [devenv](https://devenv.sh/getting-started/)+3. Clone this repository:+   ```+   git clone https://github.com/lorenzo/web-view-colonnade.git+   cd web-view-colonnade+   ```+4. Start the development shell:+   ```+   devenv shell+   ```+5. Build the project:+   ```+   cabal build+   ```+6. Run the tests:+   ```+   cabal test+   ```++### Pull Requests++1. Fork the repository+2. Create a new branch for your feature+3. Add tests for your new feature+4. Ensure all tests pass+5. Submit a pull request++## License++This project is licensed under the MIT license - see the LICENSE file for details.
+ src/WebView/Colonnade.hs view
@@ -0,0 +1,372 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeApplications #-}+++module WebView.Colonnade+  ( -- * What is this?+    -- $whatis+    -- * How to use this library+    -- $use++    -- * Encoding functions+    encodeHtmlTable+  , encodeCellTable+  , encodeTable+    -- * Cell+  , Cell(..)+  , charCell+  , stringCell+  , textCell+  , htmlCell+  , htmlFromCell+  ) where++import Colonnade (Colonnade)+import qualified Web.View.View as V+import qualified Web.View.Element as E+import qualified Colonnade.Encode as E+import qualified Data.Text as T+import Data.String (IsString(..))+import Data.Foldable (for_)+import Web.View.Types (Mod)++{- $whatis+  Build HTML tables using @web-view@ and @colonnade@. This module provides+  functionality similar to @lucid-colonnade@ and @blaze-colonnade@ but for the+  web-view library.+-}++{- $use++  = Usage++  We start with a few necessary imports and some example data types:++  >>> :set -XOverloadedStrings+  >>> import Data.Monoid (mconcat,(<>))+  >>> import Data.Char (toLower)+  >>  import qualified Data.Text as T+  >>> import Data.Profunctor (Profunctor(lmap))+  >>> import Colonnade (Colonnade,Headed,Headless,headed)+  >>> import Web.View+  >>> import qualified Web.View.Style as V +  >>> import qualified Web.View.Types as V+  >>> data Department = Management | Sales | Engineering deriving (Show,Eq)+  >>> data Employee = Employee { name :: T.Text, department :: Department, age :: Int }++  We define some employees that we will display in a table:++  >>> :{+  let employees =+        [ Employee "Thaddeus" Sales 34+        , Employee "Lucia" Engineering 33+        , Employee "Pranav" Management 57+        ]+  :}++  Let's build a table that displays the name and the age+  of an employee. Additionally, we will emphasize the names of+  engineers using a @\<strong\>@ tag.++  >>> :{+  let tableEmpA :: Colonnade Headed Employee (View c ())+      tableEmpA = mconcat+        [ headed "Name" $ \emp -> case department emp of+            Engineering -> el bold (text (name emp))+            _ -> text (name emp)+        , headed "Age" (text . T.pack . show . age)+        ]+  :}++  The type signature of @tableEmpA@ is inferrable but is written+  out for clarity in this example. Note that the first+  argument to 'headed' can be passed as a string literal due to the @OverloadedStrings@ extension.+  Let's continue:++  >>> let customAttrs = V.extClass "stylish-table" <> V.att "id" "main-table"+  >>> renderText (encodeHtmlTable customAttrs tableEmpA employees)+  <table class='stylish-table' id='main-table'>+    <thead>+      <tr>+        <th>Name</th>+        <th>Age</th>+      </tr>+    </thead>+    <tbody>+      <tr>+        <td>Thaddeus</td>+        <td>34</td>+      </tr>+      <tr>+        <td><div class="bold">Lucia</div></td>+        <td>33</td>+      </tr>+      <tr>+        <td>Pranav</td>+        <td>57</td>+      </tr>+    </tbody>+  </table>++  Excellent. As expected, Lucia's name is wrapped in a @\<strong\>@ tag+  since she is an engineer.++  One limitation of using @View@ as the content+  type of a 'Colonnade' is that we are unable to add attributes to+  the @\<td\>@ and @\<th\>@ elements. This library provides the 'Cell' type+  to work around this problem. A 'Cell' is just a @V.View@ content and a set+  of attributes to be applied to its parent @\<th\>@ or @\<td\>@. To illustrate+  its use, another employee table will be built. This table will+  contain a single column indicating the department of each employee. Each+  cell will be assigned a class name based on the department. Let's build a table +  that encodes departments:++  >>> :{+  let tableDept :: Colonnade Headed Department (Cell c)+      tableDept = mconcat+        [ headed "Dept." $ \d -> Cell+            (V.extClass (V.ClassName $ T.pack (map Data.Char.toLower (show d))))+            (E.text (T.pack (show d)))+        ]+  :}++  Again, @OverloadedStrings@ plays a role, this time allowing the+  literal @"Dept."@ to be accepted as a value of type 'Cell'. To avoid+  this extension, 'stringCell' could be used to upcast the 'String'.+  To try out our 'Colonnade' on a list of departments, we need to use+  'encodeCellTable' instead of 'encodeHtmlTable':++  >>> let twoDepts = [Sales,Management]+  >>> renderText (encodeCellTable customAttrs tableDept twoDepts)+  <table class='stylish-table' id='main-table'>+    <thead>+      <tr>+        <th>Dept.</th>+      </tr>+    </thead>+    <tbody>+      <tr>+        <td class='sales'>Sales</td>+      </tr>+      <tr>+        <td class='management'>Management</td>+      </tr>+    </tbody>+  </table>++  The attributes on the @\<td\>@ elements show up as they are expected to.+  Now, we take advantage of the @Profunctor@ instance of 'Colonnade' to allow+  this to work on @Employee@\'s instead:++  >>> :t lmap+  lmap :: Profunctor p => (a -> b) -> p b c -> p a c+  >>> let tableEmpB = lmap department tableDept+  >>> :t tableEmpB+  tableEmpB :: Colonnade Headed Employee (Cell c)+  >>> renderText (encodeCellTable customAttrs tableEmpB employees)+  <table class='stylish-table' id='main-table'>+    <thead>+      <tr>+        <th>Dept.</th>+      </tr>+    </thead>+    <tbody>+      <tr>+        <td class='sales'>Sales</td>+      </tr>+      <tr>+        <td class='engineering'>Engineering</td>+      </tr>+      <tr>+        <td class='management'>Management</td>+      </tr>+    </tbody>+  </table>++  This table shows the department of each of our three employees, additionally+  making a lowercased version of the department into a class name for the @\<td\>@.+  This table is nice for illustrative purposes, but it does not provide all the+  information that we have about the employees. If we combine it with the+  earlier table we wrote, we can present everything in the table. One small+  roadblock is that the types of @tableEmpA@ and @tableEmpB@ do not match, which+  prevents a straightforward monoidal append:++  >>> :t tableEmpA+  tableEmpA :: Colonnade Headed Employee (V.View c ())+  >>> :t tableEmpB+  tableEmpB :: Colonnade Headed Employee (Cell c)++  We can upcast the content type with 'fmap':++  >>> let tableEmpC = fmap htmlCell tableEmpA <> tableEmpB+  >>> :t tableEmpC+  tableEmpC :: Colonnade Headed Employee (Cell c)+  >>> renderText (encodeCellTable customAttrs tableEmpC employees)+  <table class='stylish-table' id='main-table'>+    <thead>+      <tr>+        <th>Name</th>+        <th>Age</th>+        <th>Dept.</th>+      </tr>+    </thead>+    <tbody>+      <tr>+        <td>Thaddeus</td>+        <td>34</td>+        <td class='sales'>Sales</td>+      </tr>+      <tr>+        <td><strong>Lucia</strong></td>+        <td>33</td>+        <td class='engineering'>Engineering</td>+      </tr>+      <tr>+        <td>Pranav</td>+        <td>57</td>+        <td class='management'>Management</td>+      </tr>+    </tbody>+  </table>+-}++-- | A table cell with attributes and content+data Cell c = Cell+  { cellAttributes :: Mod c  -- ^ Attributes for the td/th element+  , cellHtml :: V.View c ()      -- ^ Content inside the cell+  }++instance IsString (Cell c) where+  fromString = stringCell++instance Semigroup (Cell c) where+  Cell attrs1 content1 <> Cell attrs2 content2 = +    Cell (attrs1 <> attrs2) (content1 >> content2)++instance Monoid (Cell c) where+  mempty = Cell mempty (pure ())+  mappend = (<>)++-- | Create a cell from HTML content+htmlCell :: V.View c () -> Cell c+htmlCell content = Cell mempty content++-- | Create a cell from a string+stringCell :: String -> Cell c+stringCell = htmlCell . E.text . T.pack++-- | Create a cell from a character+charCell :: Char -> Cell c+charCell = stringCell . pure++-- | Create a cell from text+textCell :: T.Text -> Cell c+textCell = htmlCell . E.text++-- | Convert a cell to an HTML element+htmlFromCell :: (Mod c -> V.View c () -> V.View c ()) -> (Cell c) -> V.View c ()+htmlFromCell f (Cell attrs content) = f attrs content++-- | Encode a table with HTML content+encodeHtmlTable ::+  forall h f x c.+  (E.Headedness h, Foldable f) =>+  -- | Attributes of @\<table\>@ element+  Mod c ->+  -- | How to encode data as columns+  Colonnade h x (V.View c ()) ->+  -- | Collection of data+  f x ->+  V.View c ()+encodeHtmlTable =+  encodeTable+    (E.headednessPure (mempty, mempty))+    mempty+    (const mempty)+    (\tagFn content -> tagFn mempty content)++-- | Encode a table with cells that may have attributes+encodeCellTable ::+  forall h f x c.+  (E.Headedness h, Foldable f) =>+  -- | Attributes of @\<table\>@ element+  Mod c ->+  -- | How to encode data as columns+  Colonnade h x (Cell c) ->+  -- | Collection of data+  f x ->+  V.View c ()+encodeCellTable =+  encodeTable+    (E.headednessPure (mempty, mempty))+    mempty+    (const mempty)+    htmlFromCell++{- | Encode a table. This handles a very general case and+  is seldom needed by users. One of the arguments provided is+  used to add attributes to the generated @\<tr\>@ elements.+-}+encodeTable ::+  forall h f x v c.+  (E.Headedness h, Foldable f) =>+  -- | Attributes and structure for header section+  h (Mod c, Mod c) ->+  -- | Attributes for tbody element+  Mod c ->+  -- | Attributes for each tr element+  (x -> Mod c) ->+  -- | Cell wrapper function+  ((Mod c -> V.View c () -> V.View c ()) -> v -> V.View c ()) ->+  -- | Table attributes+  Mod c ->+  -- | How to encode data as columns+  Colonnade h x v ->+  -- | Collection of data+  f x ->+  V.View c ()+encodeTable mtheadAttrs tbodyAttrs trAttrs wrapContent tableAttrs colonnade xs =+  V.tag "table" tableAttrs $ do+    d1 <- case E.headednessExtractForall of+      Nothing -> pure mempty+      Just extractForall -> do+        let (theadAttrs, theadTrAttrs) = extract mtheadAttrs+        V.tag "thead" theadAttrs $+          V.tag "tr" theadTrAttrs $ do+            foldlMapM' (wrapContent (V.tag "th") . extract . E.oneColonnadeHead) (E.getColonnade colonnade)+        where+          extract :: forall y. h y -> y+          extract = E.runExtractForall extractForall+    d2 <- encodeBody trAttrs wrapContent tbodyAttrs colonnade xs+    pure (d1 <> d2)++foldlMapM' :: forall g b a m. (Foldable g, Monoid b, Monad m) => (a -> m b) -> g a -> m b+foldlMapM' f xs = foldr f' pure xs mempty+ where+  f' :: a -> (b -> m b) -> b -> m b+  f' x k bl = do+    br <- f x+    let !b = mappend bl br+    k b++encodeBody ::+  (Foldable f) =>+  -- | Attributes of each @\<tr\>@ element+  (a -> Mod c) ->+  -- | Wrap content and convert to 'Html'+  ((Mod c -> V.View c () -> V.View c ()) -> v -> V.View c ()) ->+  -- | Attributes of @\<tbody\>@ element+  Mod c ->+  -- | How to encode data as a row+  Colonnade h a v ->+  -- | Collection of data+  f a ->+  V.View c ()+encodeBody trAttrs wrapContent tbodyAttrs colonnade xs = do+  V.tag "tbody" tbodyAttrs $ do+    for_ xs $ \x -> do+      V.tag "tr" (trAttrs x) $ do+        E.rowMonadic colonnade (wrapContent (V.tag "td")) x
+ test/Main.hs view
@@ -0,0 +1,8 @@+module Main (main) where++import Test.Hspec+import qualified WebView.ColonnadeSpec++main :: IO ()+main = hspec $ do+  describe "WebView.Colonnade" WebView.ColonnadeSpec.spec
+ test/WebView/ColonnadeSpec.hs view
@@ -0,0 +1,174 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-}+module WebView.ColonnadeSpec (spec) where++import Test.Hspec+import WebView.Colonnade+import qualified Web.View.View as V+import qualified Web.View.Style as V+import qualified Web.View.Element as E+import Web.View (renderText)+import qualified Colonnade as C+import qualified Colonnade.Encode as E+import qualified Data.Text as T+import Data.String (IsString(fromString))+import Data.String.Interpolate (i)++data Person = Person+  { name :: T.Text+  , age :: Int+  } deriving (Show, Eq)++spec :: Spec+spec = do+  describe "Cell" $ do+    it "implements IsString" $ do+      let s = "test"+          cell = fromString s :: Cell c+      let result = renderText (htmlFromCell (\_ -> V.tag "td" mempty) cell)+      result `shouldBe` [i|<td>#{s}</td>|]++    it "implements Semigroup" $ do+      let s1 = "test1"+          s2 = "test2"+          cell1 = fromString s1 :: Cell c+          cell2 = fromString s2 :: Cell c+          combined = cell1 <> cell2+      let result = renderText (htmlFromCell (\_ -> V.tag "td" mempty) combined)+      result `shouldBe` [i|<td>+  #{s1}#{s2}</td>|]++    it "implements Monoid" $ do+      let s = "test"+          cell = fromString s :: Cell c+      let result1 = renderText (htmlFromCell (\_ -> V.tag "td" mempty) (cell <> mempty))+          result2 = renderText (htmlFromCell (\_ -> V.tag "td" mempty) cell)+      result1 `shouldBe` result2++  describe "encodeHtmlTable" $ do+    let personColonnade = mconcat+          [ C.headed "Name" (E.text . name)+          , C.headed "Age" (E.text . T.pack . show . age)+          ]+        people = [Person "Alice" 30, Person "Bob" 25]++    it "generates correct HTML structure" $ do+      let html = encodeHtmlTable (V.extClass "table") personColonnade people+      let result = renderText html+      result `shouldBe` [i|<table class='table'>+  <thead>+    <tr>+      <th>Name</th>+      <th>Age</th>+    </tr>+  </thead>+  <tbody>+    <tr>+      <td>Alice</td>+      <td>30</td>+    </tr>+    <tr>+      <td>Bob</td>+      <td>25</td>+    </tr>+  </tbody>+</table>|]++    it "preserves table attributes" $ do+      let attr = "class"+          val = "test-table"+          html = encodeHtmlTable (V.att attr val) personColonnade []+          rendered = renderText html+      rendered `shouldBe` [i|<table class='#{val}'>+  <thead>+    <tr>+      <th>Name</th>+      <th>Age</th>+    </tr>+  </thead>+  <tbody></tbody>+</table>|]++  describe "encodeCellTable" $ do+    let personColonnade = mconcat+          [ C.headed "Name" (\p -> Cell (V.extClass "name") (E.text $ name p))+          , C.headed "Age" (\p -> Cell (V.extClass "age") (E.text . T.pack . show $ age p))+          ]+        people = [Person "Alice" 30, Person "Bob" 25]++    it "preserves cell attributes" $ do+      let html = encodeCellTable mempty personColonnade people+          rendered = renderText html+      rendered `shouldBe` [i|<table>+  <thead>+    <tr>+      <th>Name</th>+      <th>Age</th>+    </tr>+  </thead>+  <tbody>+    <tr>+      <td class='name'>Alice</td>+      <td class='age'>30</td>+    </tr>+    <tr>+      <td class='name'>Bob</td>+      <td class='age'>25</td>+    </tr>+  </tbody>+</table>|]++  describe "Cell constructors" $ do+    it "charCell creates valid HTML" $ do+      let c = 'a'+          cell = charCell c+      let result = renderText (htmlFromCell (\_ -> V.tag "td" mempty) cell)+      result `shouldBe` [i|<td>#{c}</td>|]++    it "stringCell creates valid HTML" $ do+      let s = "test"+          cell = stringCell s+      let result = renderText (htmlFromCell (\_ -> V.tag "td" mempty) cell)+      result `shouldBe` [i|<td>#{s}</td>|]++    it "textCell creates valid HTML" $ do+      let t = "test" :: T.Text+          cell = textCell t+      let result = renderText (htmlFromCell (\_ -> V.tag "td" mempty) cell)+      result `shouldBe` [i|<td>#{t}</td>|]++  describe "encodeTable" $ do+    let personColonnade = mconcat+          [ C.headed "Name" (E.text . name)+          , C.headed "Age" (E.text . T.pack . show . age)+          ]+        people = [Person "Alice" 30, Person "Bob" 25]++    it "applies all attribute functions" $ do+      let html = encodeTable+            (E.headednessPure (V.extClass "head", V.extClass "head-row"))+            (V.extClass "body")+            (\_ -> V.extClass "row")+            (\tagFn content -> tagFn (V.extClass "i") content)+            (V.extClass "table")+            personColonnade+            people+          rendered = renderText html+      rendered `shouldBe` [i|<table class='table'>+  <thead class='head'>+    <tr class='head-row'>+      <th class='i'>Name</th>+      <th class='i'>Age</th>+    </tr>+  </thead>+  <tbody class='body'>+    <tr class='row'>+      <td class='i'>Alice</td>+      <td class='i'>30</td>+    </tr>+    <tr class='row'>+      <td class='i'>Bob</td>+      <td class='i'>25</td>+    </tr>+  </tbody>+</table>|]
+ web-view-colonnade.cabal view
@@ -0,0 +1,116 @@+cabal-version:   3.4++-- The cabal-version field refers to the version of the .cabal specification,+-- and can be different from the cabal-install (the tool) version and the+-- Cabal (the library) version you are using. As such, the Cabal (the library)+-- version used must be equal or greater than the version stated in this field.+-- Starting from the specification version 2.2, the cabal-version field must be+-- the first thing in the cabal file.++-- Initial package description 'web-view-colonnade' generated by+-- 'cabal init'. For further documentation, see:+--   http://haskell.org/cabal/users-guide/+--+-- The name of the package.+name:            web-view-colonnade++-- The package version.+-- See the Haskell package versioning policy (PVP) for standards+-- guiding when and how versions should be incremented.+-- https://pvp.haskell.org+-- PVP summary:     +-+------- breaking API changes+--                  | | +----- non-breaking API additions+--                  | | | +--- code changes with no API change+version:         0.1.0.0++-- A short (one-line) description of the package.+synopsis:        Build HTML tables using web-view and colonnade.+homepage:        https://github.com/lorenzo/web-view-colonnade++-- A longer description of the package.+description:+  Build HTML tables using web-view and colonnade. This module provides+  functionality similar to @lucid-colonnade@ and @blaze-colonnade@ but for the+  web-view library.++-- The license under which the package is released.+license:         MIT++-- The file containing the license text.+license-file:    LICENSE++-- The package author(s).+author:          José Lorenzo Rodríguez++-- An email address to which users can send suggestions, bug reports, and patches.+maintainer:      lorenzo@users.noreply.github.com++-- A copyright notice.+category:        Web+build-type:      Simple++-- Extra doc files to be distributed with the package, such as a CHANGELOG or a README.+extra-doc-files: README.md++-- Extra source files to be distributed with the package, such as examples, or a tutorial module.+-- extra-source-files:++common warnings+  ghc-options: -Wall++common deps+  build-depends:+    , base        >=4.18.2.1 && <5+    , colonnade   >=1.2.0    && <1.3+    , containers  >=0.6.7    && <0.9+    , text        >=2.0.2    && <2.2+    , vector      >=0.13.2   && <0.14+    , web-view    >=0.6.0    && <0.8++library+  -- Import common warning flags.+  import:           warnings, deps++  -- Modules exported by the library.+  exposed-modules:  WebView.Colonnade++  -- Modules included in this library but not exported.+  -- other-modules:++  -- LANGUAGE extensions used by modules in this package.+  -- other-extensions:  ++  -- Directories containing source files.+  hs-source-dirs:   src++  -- Base language which the package is written in.+  default-language: GHC2021++test-suite web-view-colonnade-test+  -- Import common warning flags.+  import:           warnings, deps++  -- Base language which the package is written in.+  default-language: GHC2021++  -- Modules included in this executable, other than Main.+  other-modules:    WebView.ColonnadeSpec++  -- LANGUAGE extensions used by modules in this package.+  other-extensions: OverloadedStrings++  -- The interface type and version of the test suite.+  type:             exitcode-stdio-1.0++  -- Directories containing source files.+  hs-source-dirs:   test++  -- The entrypoint to the test suite.+  main-is:          Main.hs++  -- Test dependencies.+  build-depends:    web-view-colonnade++source-repository head+  type:     git+  location: https://github.com/lorenzo/web-view-colonnade