packages feed

clerk 0.1.0.2 → 0.1.0.3

raw patch · 4 files changed

+568/−425 lines, 4 filesdep +typed-processdep ~basedep ~bytestringdep ~containersPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: typed-process

Dependency ranges changed: base, bytestring, containers, data-default, lens, mtl, text, time, transformers, xlsx

API changes (from Hackage documentation)

- Clerk: (<|) :: FCTransform -> FormatCell -> FormatCell
- Clerk: data Builder input output a
- Clerk: data Cell a
- Clerk: ex :: Cell a -> Expr a
- Clerk: ex' :: forall b a. Expr a -> Expr b
- Clerk: instance Clerk.ToExpr (Clerk.Cell a)
- Clerk: instance Control.Monad.State.Class.MonadState Clerk.Coords (Clerk.Builder input output)
- Clerk: instance Control.Monad.Writer.Class.MonadWriter (Clerk.Template input output) (Clerk.Builder input output)
- Clerk: instance GHC.Base.Applicative (Clerk.Builder input output)
- Clerk: instance GHC.Base.Functor (Clerk.Builder input output)
- Clerk: instance GHC.Base.Functor Clerk.Cell
- Clerk: instance GHC.Base.Functor Clerk.Expr
- Clerk: instance GHC.Base.Monad (Clerk.Builder input output)
- Clerk: instance GHC.Num.Num (Clerk.Cell a)
+ Clerk: (+>) :: FormatCell -> FCTransform -> FormatCell
+ Clerk: CellRef :: Coords -> CellRef a
+ Clerk: ColumnsProperties :: ColumnsProperties -> ColumnsProperties
+ Clerk: Power :: Expr t -> Expr t -> Expr t
+ Clerk: RowBuilder :: StateT Coords (Writer (Template input output)) a -> RowBuilder input output a
+ Clerk: Template :: [CellTemplate input output] -> Template input output
+ Clerk: [$sel:col:Coords] :: Coords -> Int
+ Clerk: [$sel:row:Coords] :: Coords -> Int
+ Clerk: [$sel:unBuilder:RowBuilder] :: RowBuilder input output a -> StateT Coords (Writer (Template input output)) a
+ Clerk: [$sel:unCell:CellRef] :: CellRef a -> Coords
+ Clerk: [$sel:unColumnsProperties:ColumnsProperties] :: ColumnsProperties -> ColumnsProperties
+ Clerk: class ToExpr v
+ Clerk: columnWidthCell :: forall a input output. Maybe Double -> FormatCell -> (input -> output) -> RowBuilder input output (CellRef a)
+ Clerk: data CellTemplate input output
+ Clerk: data Transform
+ Clerk: getCol :: CellRef a -> Int
+ Clerk: getRow :: CellRef a -> Int
+ Clerk: instance Clerk.ToExpr (Clerk.CellRef a)
+ Clerk: instance Control.Monad.State.Class.MonadState Clerk.Coords (Clerk.RowBuilder input output)
+ Clerk: instance Control.Monad.Writer.Class.MonadWriter (Clerk.Template input output) (Clerk.RowBuilder input output)
+ Clerk: instance GHC.Base.Applicative (Clerk.RowBuilder input output)
+ Clerk: instance GHC.Base.Functor (Clerk.RowBuilder input output)
+ Clerk: instance GHC.Base.Monad (Clerk.RowBuilder input output)
+ Clerk: instance GHC.Num.Num (Clerk.CellRef a)
+ Clerk: newtype CellRef a
+ Clerk: newtype ColumnsProperties
+ Clerk: newtype RowBuilder input output a
+ Clerk: newtype Template input output
+ Clerk: placeInput_ :: ToCellData output => Coords -> input -> RowBuilder input output a -> SheetBuilder ()
+ Clerk: placeInputs :: ToCellData output => Coords -> [input] -> RowBuilder input output a -> SheetBuilder a
+ Clerk: toCellData :: ToCellData a => a -> CellData
+ Clerk: toExpr :: ToExpr v => v -> Expr t
+ Clerk: type FMTransform = FormattedMap -> FormattedMap
+ Clerk: type FormattedMap = Map (RowIndex, ColumnIndex) FormattedCell
+ Clerk: type InputIndex = Int
+ Clerk: type WSTransform = Worksheet -> Worksheet
+ Clerk: type ArithmeticOperator a b c = (Num a, ToExpr (b a), ToExpr (c a)) => b a -> c a -> Expr a
+ Clerk: unsafeChangeCellRefType :: forall b a. CellRef a -> CellRef b
- Clerk: (|*|) :: Num a => Expr a -> Expr a -> Expr a
+ Clerk: (|*|) :: ArithmeticOperator a b c
- Clerk: (|+|) :: Num a => Expr a -> Expr a -> Expr a
+ Clerk: (|+|) :: ArithmeticOperator a b c
- Clerk: (|-|) :: Num a => Expr a -> Expr a -> Expr a
+ Clerk: (|-|) :: ArithmeticOperator a b c
- Clerk: (|/|) :: Num a => Expr a -> Expr a -> Expr a
+ Clerk: (|/|) :: ArithmeticOperator a b c
- Clerk: (|:|) :: Cell a -> Cell b -> Expr c
+ Clerk: (|:|) :: CellRef a -> CellRef b -> Expr c
- Clerk: (|^|) :: Num a => Expr a -> Expr a -> Expr a
+ Clerk: (|^|) :: ArithmeticOperator a b c
- Clerk: ExprCell :: Cell t -> Expr t
+ Clerk: ExprCell :: CellRef t -> Expr t
- Clerk: column :: ToCellData output => FormatCell -> (input -> output) -> Builder input CellData (Cell a)
+ Clerk: column :: ToCellData output => FormatCell -> (input -> output) -> RowBuilder input CellData (CellRef a)
- Clerk: columnWidth :: ToCellData output => Double -> FormatCell -> (input -> output) -> Builder input CellData (Cell a)
+ Clerk: columnWidth :: ToCellData output => Double -> FormatCell -> (input -> output) -> RowBuilder input CellData (CellRef a)
- Clerk: columnWidth_ :: ToCellData output => Double -> FormatCell -> (input -> output) -> Builder input CellData ()
+ Clerk: columnWidth_ :: ToCellData output => Double -> FormatCell -> (input -> output) -> RowBuilder input CellData ()
- Clerk: column_ :: ToCellData output => FormatCell -> (input -> output) -> Builder input CellData ()
+ Clerk: column_ :: ToCellData output => FormatCell -> (input -> output) -> RowBuilder input CellData ()
- Clerk: infixl 5 <|
+ Clerk: infixl 5 +>
- Clerk: mkColorStyle :: Text -> FormatCell
+ Clerk: mkColorStyle :: Show a => a -> FormatCell
- Clerk: placeInput :: ToCellData output => Coords -> input -> Builder input output a -> SheetBuilder a
+ Clerk: placeInput :: ToCellData output => Coords -> input -> RowBuilder input output a -> SheetBuilder a
- Clerk: placeInputs_ :: ToCellData output => Coords -> [input] -> Builder input output a -> SheetBuilder ()
+ Clerk: placeInputs_ :: ToCellData output => Coords -> [input] -> RowBuilder input output a -> SheetBuilder ()
- Clerk: type FormatCell = Coords -> Index -> CellData -> FormattedCell
+ Clerk: type FormatCell = Coords -> InputIndex -> CellData -> FormattedCell

Files

README.md view
@@ -1,178 +1,147 @@ # clerk -`clerk` is a library for declarative spreadsheet generation using a Haskell eDSL.--It extends upon the [work](https://youtu.be/1xGoa-zEOrQ) of Kudasov by making the tables' layout more flexible.+`clerk` provides a Haskell eDSL in a library for declarative spreadsheet generation. `clerk` is built on top of the [xlsx](https://hackage.haskell.org/package/xlsx) package and extends upon the [work](https://youtu.be/1xGoa-zEOrQ) of Nickolay Kudasov by making the tables' layout more flexible.  ## Features -`clerk` produces a styled spreadsheet with some data and formulas on it. These formulas will be calculated by the target spreadsheet system.+`clerk` can be used to produce a styled spreadsheet with some data and formulas on it. These formulas are evaluated when the document is loaded into a target spreadsheet system. -The library supports+The library supports: -- typed cell references - `Cell Double`-- type-safe arithmetic operations - `(a :: Cell Double) + (b :: Cell Double)`-- range references - `a |:| b` -> `A1:B1`-- formulas - `(e :: Expr Double) = "SUM" |$| [(a |:| b)]` -> `SUM(A1:B1)`-- conditional styles, formatting, column widths+- Typed cell references. Example: `CellRef Double`.+- Type-safe arithmetic operations with them. Example: `(a :: CellRef Double) + (b :: CellRef Double)` produces a `CellRef Double`.+- Constructing expressions with given types. Example: `(e :: Expr Double) = "SUM" |$| [a |:| b]`, `e` translates to `SUM(A1:B1)` (actual value depends on the values of `a` and `b`).+- Conditional styles, formatting, column widths. -The example below demonstrates some of these features.+The example below demonstrates most of these features.  ## Example -This is a demo program that uses `clerk` to produce an `xlsx` file that looks as follows:+The goal: describe and generate a spreadsheet that calculates the pressure data given some volume data and constants. +The source code for this example is available in the [example](./example) directory.+The program produces an `xlsx` file that looks as follows:+ <img src = "https://raw.githubusercontent.com/deemp/clerk/master/README/demoValues.png" width = "80%">  Alternatively, with formulas enabled:  <img src = "https://raw.githubusercontent.com/deemp/clerk/master/README/demoFormulas.png" width = "80%"> -This file has a sheet with several tables. These are tables for-constants' header, a table per a constant's value (three of them), volume & pressure header, volume & pressure values.-Let's see how we can construct such a sheet.+The below sections describe how such a spreadsheet can be constructed. +### Extensions++We'll need several language extensions.+<!-- FOURMOLU_DISABLE -->++```haskell+{-# LANGUAGE OverloadedRecordDot #-} -- access the fields of records like a.b+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE InstanceSigs #-}+```++<!-- FOURMOLU_ENABLE -->+ ### Imports -First, we import the necessary stuff.+And import the necessary stuff.  ```haskell module Main (main) where+ import Clerk import Codec.Xlsx qualified as X import Codec.Xlsx.Formatted qualified as X import Control.Lens ((%~), (&), (?~))+import Control.Monad (void) import Data.ByteString.Lazy qualified as L import Data.Text qualified as T import Data.Time.Clock.POSIX (getPOSIXTime)-import Control.Monad (void) ``` -### Inputs+### Tables -Following that, we declare a number of data types that we'll use to store the input values.+The tables that we'd like to construct are: -A type for constants' headers.+- A table per a constant's value (three of them)+- Volume & pressure table+- Constants' header+- Volume & pressure header -```haskell-data ConstantsHeader = ConstantsHeader-    { hConstant :: String-    , hSymbol :: String-    , hValue :: String-    , hUnits :: String-    }+#### Constants' values -constantsHeader :: ConstantsHeader-constantsHeader =-    ConstantsHeader-        { hConstant = "constant"-        , hSymbol = "symbol"-        , hValue = "value"-        , hUnits = "units"-        }-```+<img src = "https://raw.githubusercontent.com/deemp/clerk/master/README/constants.png" width = "50%"> -A type for constants' data.+In our case, each constant has the same type of the numeric value - `Double`.+However, it might be the case that in another set of constants, they'll have different types.+That's why, in our case, we'll construct a table with a single row per a constant and later stack the constants' tables together.+We can keep a constant's data in a record.  ```haskell-data ConstantsData a = ConstantsData-    { name :: String-    , symbol :: String-    , value :: a-    , units :: String-    }+data ConstantData a = ConstantData+  { constantName :: String+  , constantSymbol :: String+  , constantValue :: a+  , constantUnits :: String+  } ``` -Additionally, we declare a helper type that will store all constants together.+Next, we can group the constants.  ```haskell-data ConstantsInput = ConstantsInput-    { gas :: ConstantsData Double-    , nMoles :: ConstantsData Double-    , temperature :: ConstantsData Double-    }+data Constants f = Constants+  { gasConstant :: f Double+  , numberOfMoles :: f Double+  , temperature :: f Double+  } +type ConstantsInput = Constants ConstantData+ constants :: ConstantsInput constants =-    ConstantsInput-        { gas = ConstantsData "GAS CONSTANT" "R" 0.08206 "L.atm/mol.K"-        , nMoles = ConstantsData "NUMBER OF MOLES" "n" 1 "moles"-        , temperature = ConstantsData "TEMPERATURE(K)" "T" 273.2 "K"-        }-```--A type for the Volume & Pressure header.--```haskell-data ValuesHeader = ValuesHeader-    { hVolume :: String-    , hPressure :: String+  Constants+    { gasConstant = ConstantData "GAS CONSTANT" "R" 0.08206 "L.atm/mol.K"+    , numberOfMoles = ConstantData "NUMBER OF MOLES" "n" 1 "moles"+    , temperature = ConstantData "TEMPERATURE(K)" "T" 273.2 "K"     }--valuesHeader :: ValuesHeader-valuesHeader =-    ValuesHeader-        { hVolume = "VOLUME (L)"-        , hPressure = "PRESSURE (atm)"-        } ``` -The last type is for volume inputs. We just generate them--```haskell-newtype Volume = Volume-    { volume :: Double-    }--volumeData :: [Volume]-volumeData = take 10 $ Volume <$> [1 ..]-```--### Styles--Following the headers and data types, we define the styles. Let's start with colors.-We select several color codes and store them into `colors`+Furthermore, we'd like to style the constants' tables, so let's prepare the styles. We'll reuse these styles in other tables.  ```haskell-data Colors = Colors-    { lightBlue :: T.Text-    , lightGreen :: T.Text-    , blue :: T.Text-    , green :: T.Text-    }--colors :: Colors-colors =-    Colors-        { lightGreen = "90CCFFCC"-        , lightBlue = "90CCFFFF"-        , blue = "FF99CCFF"-        , green = "FF00FF00"-        }-```--Next, we convert them to `FormatCell` function+data Colors = LightBlue | LightGreen | Blue | Green+instance Show Colors where+  show :: Colors -> String+  show = \case+    LightBlue -> "90CCFFFF"+    LightGreen -> "90CCFFCC"+    Blue -> "FF99CCFF"+    Green -> "FF00FF00" -```haskell-colorBlue :: FormatCell-colorBlue = mkColorStyle colors.blue+blue :: FormatCell+blue = mkColorStyle Blue -colorLightBlue :: FormatCell-colorLightBlue = mkColorStyle colors.lightBlue+lightBlue :: FormatCell+lightBlue = mkColorStyle LightBlue -colorGreen :: FormatCell-colorGreen = mkColorStyle colors.green+green :: FormatCell+green = mkColorStyle Green -colorMixed :: FormatCell-colorMixed coords idx = mkColorStyle (if even idx then colors.lightGreen else colors.lightBlue) coords idx+mixed :: FormatCell+mixed coords idx = mkColorStyle (if even idx then LightGreen else LightBlue) coords idx ``` -Additionally, we compose a transform for the number format+Additionally, we compose a transformation of a `FormatCell` for the number format  ```haskell--- | allow 2 decimal digits-nf2decimal :: FCTransform-nf2decimal fc = fc & X.formattedFormat %~ (\ff -> ff & X.formatNumberFormat ?~ X.StdNumberFormat X.Nf2Decimal)+use2decimalDigits :: FCTransform+use2decimalDigits fcTransform =+  fcTransform & X.formattedFormat %~ (\format -> format & X.formatNumberFormat ?~ X.StdNumberFormat X.Nf2Decimal) ```  And a transform for centering the cell contents@@ -182,101 +151,118 @@ alignCenter = horizontalAlignment X.CellHorizontalAlignmentCenter ``` -### `Builder`s+Now, we can make a `RowBuilder` for a constant.+We'll later use this builder for each constant separately. -Now, we are able to compose the `Builder`s for tables.+We get a pair of outputs: -A builder for the constants header.+- Top left cell of a constant's table. That is, the cell with that constant's name.+- The value of the constant. -<img src = "https://raw.githubusercontent.com/deemp/clerk/master/README/constantsHeader.png" width = "50%">+Later, the outputs of this and other `RowBuilder`s will be used to relate the positions of tables on a sheet.  ```haskell-constantsHeaderBuilder :: Builder ConstantsHeader CellData (Coords, Coords)-constantsHeaderBuilder = do-    tl <- columnWidth 20 (alignCenter <| colorBlue) hConstant-    columnWidth_ 8 (alignCenter <| colorBlue) hSymbol-    column_ (alignCenter <| colorBlue) hValue-    tr <- columnWidth 13 (alignCenter <| colorBlue) hUnits-    return (unCell tl, unCell tr)+constantBuilder :: ToCellData a => RowBuilder (ConstantData a) CellData (Coords, CellRef a)+constantBuilder = do+  refTopLeft <- column lightBlue constantName+  column_ lightBlue constantSymbol+  refValue <- column (lightBlue +> use2decimalDigits) constantValue+  column_ lightBlue constantUnits+  return (unCell refTopLeft, refValue) ``` -A builder for a constant. We'll use this builder for each constant separately-as each constant produces cells of a specific type.+#### Volume & Pressure values -<img src = "https://raw.githubusercontent.com/deemp/clerk/master/README/constants.png" width = "50%">+<img src = "https://raw.githubusercontent.com/deemp/clerk/master/README/valuesFormulas.png" width = "50%"> +To fill this table, we'll take the some data and combine it with the constants.+ ```haskell-constantBuilder :: forall a. ToCellData a => Builder (ConstantsData a) CellData (Coords, Cell a)-constantBuilder = do-    topLeft <- column colorLightBlue name-    column_ colorLightBlue symbol-    value <- column (nf2decimal <| colorLightBlue) value-    column_ colorLightBlue units-    return (unCell topLeft, value)+newtype Volume = Volume {volume :: Double}++volumeData :: [Volume]+volumeData = take 10 $ Volume <$> [1 ..] ``` -A builder for values' header.+To pass the constants' references in a structured way, we make a helper type. -<img src = "https://raw.githubusercontent.com/deemp/clerk/master/README/valuesHeader.png" width = "50%">+```haskell+data ConstantsRefs = ConstantsRefs+  { refGas :: CellRef Double+  , refNumberOfMoles :: CellRef Double+  , refTemperature :: CellRef Double+  }+``` +Next, we define a function to produce a builder for volume and pressure.+ ```haskell-valuesHeaderBuilder :: Builder ValuesHeader CellData Coords-valuesHeaderBuilder = do-    tl <- columnWidth 12 colorGreen hVolume-    columnWidth_ 16 colorGreen hPressure-    return (unCell tl)+valuesBuilder :: ConstantsRefs -> RowBuilder Volume CellData ()+valuesBuilder ConstantsRefs{..} = do+  refVolume <- column mixed volume+  let pressure' = refGas |*| refNumberOfMoles |*| refTemperature |/| refVolume+  column_ (mixed +> use2decimalDigits) (const pressure') ``` -A builder for volume & pressure (formulas enabled)+#### Constants' header -<img src = "https://raw.githubusercontent.com/deemp/clerk/master/README/valuesFormulas.png" width = "50%">+<img src = "https://raw.githubusercontent.com/deemp/clerk/master/README/constantsHeader.png" width = "50%"> -To pass values in a structured way, we make a helper type.+We won't use records here. Instead, we'll put the names of the columns straight into the `RowBuilder`. +The outputs will be the coordinates of the top left cell and the top right cell of this table.+ ```haskell-data ConstantsValues = ConstantsValues-    { gas :: Cell Double-    , nMoles :: Cell Double-    , temperature :: Cell Double-    }+constantsHeaderBuilder :: RowBuilder () CellData (Coords, Coords)+constantsHeaderBuilder = do+  refTopLeft <- columnWidth 20 (blue +> alignCenter) (const "constant")+  columnWidth_ 8 (blue +> alignCenter) (const "symbol")+  column_ (blue +> alignCenter) (const "value")+  refTopRight <- columnWidth 13 (blue +> alignCenter) (const "units")+  return (unCell refTopLeft, unCell refTopRight) ``` -Next, we define a function to produce a builder.+#### Volume & Pressure header +<img src = "https://raw.githubusercontent.com/deemp/clerk/master/README/valuesHeader.png" width = "50%">++For this header, we'll also put the names of columns straight inside the builder.+ ```haskell-valuesBuilder :: ConstantsValues -> Builder Volume CellData ()-valuesBuilder cv = do-    volume' <- column colorMixed volume-    let pressure' = ex cv.gas |*| ex cv.nMoles |*| ex cv.temperature |/| ex volume'-    column_ (nf2decimal <| colorMixed) (const pressure')+valuesHeaderBuilder :: RowBuilder () CellData Coords+valuesHeaderBuilder = do+  tl <- columnWidth 12 green (const "VOLUME (L)")+  columnWidth_ 16 green (const "PRESSURE (atm)")+  return (unCell tl) ``` -### `SheetBuilder`+### Sheet builder -The `SheetBuilder` is used to place builders onto a sheet and glue them together+The `SheetBuilder` is used to place `RowBuilder`s onto a sheet and glue them together.+Inside `SheetBuilder`, when a `RowBuilder` is placed onto a sheet, we can use the+references that it produces in the subsequent expressions.  ```haskell full :: SheetBuilder () full = do-    (constantsHeaderTL, constantsHeaderTR) <- placeInput (Coords 2 2) constantsHeader constantsHeaderBuilder-    (gasTL, gas) <- placeInput (overRow (+ 2) constantsHeaderTL) constants.gas constantBuilder-    (nMolesTL, nMoles) <- placeInput (overRow (+ 1) gasTL) constants.nMoles constantBuilder-    temperature <- snd <$> placeInput (overRow (+ 1) nMolesTL) constants.temperature constantBuilder-    valuesHeaderTL <- placeInput (overCol (+ 2) constantsHeaderTR) valuesHeader valuesHeaderBuilder-    placeInputs_ (overRow (+ 2) valuesHeaderTL) volumeData (valuesBuilder $ ConstantsValues{..})+  (constantsHeaderTL, constantsHeaderTR) <- placeInput (Coords 2 2) () constantsHeaderBuilder+  (gasTL, gas) <- placeInput (overRow (+ 2) constantsHeaderTL) constants.gasConstant constantBuilder+  (nMolesTL, nMoles) <- placeInput (overRow (+ 1) gasTL) constants.numberOfMoles constantBuilder+  temperature <- snd <$> placeInput (overRow (+ 1) nMolesTL) constants.temperature constantBuilder+  valuesHeaderTL <- placeInput (overCol (+ 2) constantsHeaderTR) () valuesHeaderBuilder+  placeInputs_ (overRow (+ 2) valuesHeaderTL) volumeData (valuesBuilder $ ConstantsRefs gas nMoles temperature) ```  ### Result -Now, we can write the result and get the spreadsheet images that you've seen at the top of this tutorial.+Finally, we can write the result and get the spreadsheet like the one that you've seen at the top of this tutorial.  ```haskell writeWorksheet :: SheetBuilder a -> String -> IO () writeWorksheet tb name = do-    ct <- getPOSIXTime-    let-        xlsx = composeXlsx [("List 1", void tb)]-    L.writeFile ("example-" <> name <> ".xlsx") $ X.fromXlsx ct xlsx+  ct <- getPOSIXTime+  let xlsx = composeXlsx [(T.pack "List 1", void tb)]+  L.writeFile ("example-" <> name <> ".xlsx") $ X.fromXlsx ct xlsx  writeEx :: IO () writeEx = writeWorksheet full "1"@@ -301,20 +287,34 @@  ## Contribute -### Prerequisites+This project provides a dev environment via a `Nix` flake. -As this project uses `Nix` for dev environment, study the following prerequisites to set up the project+1. With [flakes enabled](https://nixos.wiki/wiki/Flakes#Enable_flakes), run: -- [Prerequisites](https://github.com/deemp/flakes#prerequisites)-- `Haskell` project [template](https://github.com/deemp/flakes/tree/main/templates/codium/haskell#readme)-- [Haskell](https://github.com/deemp/flakes/blob/main/README/Haskell.md)+    ```console+    nix develop+    cabal build+    ``` -Next, run+1. This `README.md` is generated from several files. If you edit them, re-generate it. -```sh-nix develop-write-settings-json-codium .-```+    ```console+    cabal test docs+    ``` -and open a `Haskell` file. `HLS` should soon start giving you hints.+1. (Optionally) Start `VSCodium` with `Haskell` extensions.++    1. Write settings and run `VSCodium`.++        ```console+        nix run .#writeSettings+        nix run .#codium .+        ```++    1. Open a `Haskell` file. `Haskell Language Server` should soon start giving you hints.++1. Study these links if you'd like to learn more about the tools used in this flake:++    - [Prerequisites](https://github.com/deemp/flakes#prerequisites)+    - `Haskell` project [template](https://github.com/deemp/flakes/tree/main/templates/codium/haskell#readme)+    - [Haskell](https://github.com/deemp/flakes/blob/main/README/Haskell.md)
clerk.cabal view
@@ -1,13 +1,16 @@ cabal-version: 1.12 --- This file has been generated from package.yaml by hpack version 0.35.0.+-- This file has been generated from package.yaml by hpack version 0.35.1. -- -- see: https://github.com/sol/hpack  name:           clerk-version:        0.1.0.2-synopsis:       Declaratively describe spreadsheets and generate xlsx-description:    Please see the README on GitHub at <https://github.com/deemp/clerk#readme>+version:        0.1.0.3+synopsis:       Declaratively describe spreadsheets+description:    `clerk` provides a Haskell eDSL and a library for declaratively describing the spreadsheets. +                `clerk` is built on top of the [xlsx](https://hackage.haskell.org/package/xlsx) package +                and extends upon the [work](https://youtu.be/1xGoa-zEOrQ) of Nickolay Kudasov.+                See the [README](https://github.com/deemp/clerk#readme) for an example of `clerk` usage and further info. category:       spreadsheet homepage:       https://github.com/deemp/clerk#readme bug-reports:    https://github.com/deemp/clerk/issues@@ -32,31 +35,38 @@       Paths_clerk   hs-source-dirs:       src-  default-extensions:-      DataKinds-      DeriveFunctor-      FlexibleContexts-      FlexibleInstances-      OverloadedStrings-      GeneralizedNewtypeDeriving-      RankNTypes-      InstanceSigs-      NamedFieldPuns-      RecordWildCards-      TupleSections-      MonoLocalBinds-      TypeSynonymInstances-      DuplicateRecordFields   ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints   build-depends:-      base >=4.9.0.0 && <5.0-    , bytestring >=0.10.8.0-    , containers >=0.5.0.0-    , data-default >=0.7.1.1-    , lens >=3.8 && <5.3-    , mtl >=2.1-    , text >=0.11.3.1-    , time >=1.4.0.1-    , transformers >=0.3.0.0-    , xlsx >=1.1.0.1+      base >=4.9 && <5.0+    , bytestring+    , containers+    , data-default+    , lens+    , mtl+    , text+    , time+    , transformers+    , xlsx+  default-language: Haskell2010++test-suite docs+  type: exitcode-stdio-1.0+  main-is: test/Docs.hs+  other-modules:+      Paths_clerk+  ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Wincomplete-uni-patterns -Wmissing-export-lists -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints+  build-tool-depends:+      lima:lima ==0.1.*+  build-depends:+      base+    , bytestring+    , containers+    , data-default+    , lens+    , mtl+    , text+    , time+    , transformers+    , typed-process+    , xlsx   default-language: Haskell2010
src/Clerk.hs view
@@ -1,28 +1,86 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DeriveFunctor #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE MonoLocalBinds #-}+{-# LANGUAGE NamedFieldPuns #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TupleSections #-} {-# OPTIONS_GHC -Wno-redundant-constraints #-} {-# OPTIONS_GHC -Wno-unused-top-binds #-} +-- | @Clerk@ library+ module Clerk (-  Builder,-  Cell (unCell),-  CellData,-  Coords (Coords),-  FCTransform,+  -- * Coords+  -- $Coords+  Coords (..),++  -- * Cell references+  -- $CellRef+  CellRef (..),+  getCol,+  getRow,+  overCol,+  overRow,+  unsafeChangeCellRefType,++  -- * Cell formatting+  -- $Formatting+  InputIndex,   FormatCell,-  ToCellData,-  SheetBuilder (..),-  column,+  CellTemplate,+  FormattedMap,+  FMTransform,+  WSTransform,+  Transform,+  FCTransform,+  horizontalAlignment,+  mkColorStyle,++  -- * Templates+  -- $Templates+  RowBuilder (..),+  Template (..),+  -- runBuilder,+  -- evalBuilder,+  -- execBuilder,+  -- RenderTemplate,+  -- RenderBuilderInputs,+  -- RenderBuilderInput,+  -- renderBuilderInputs,+  -- renderTemplate,++  -- * Columns+  -- $Columns+  ColumnsProperties (..),+  columnWidthCell,   columnWidth,   columnWidth_,+  column,   column_,-  composeXlsx,-  ex,-  ex',-  horizontalAlignment,-  mkColorStyle,-  overCol,-  overRow,-  placeInput,++  -- * Sheet builder+  -- $SheetBuilder+  SheetBuilder (..),+  placeInputs,   placeInputs_,+  placeInput,+  placeInput_,++  -- * Expressions+  -- $Expressions+  Expr (..),+  ToExpr (..),+  ArithmeticOperator,   (|+|),   (|-|),   (|*|),@@ -30,12 +88,20 @@   (|:|),   (|^|),   (|$|),-  (<|),-  Expr (..),+  (+>),++  -- * Cells+  -- $Cells+  CellData,+  ToCellData (..),++  -- * Produce xlsx+  -- $Xlsx+  composeXlsx, ) where -import qualified Codec.Xlsx as X-import qualified Codec.Xlsx.Formatted as X+import Codec.Xlsx qualified as X+import Codec.Xlsx.Formatted qualified as X import Control.Lens (Identity (runIdentity), (%~), (&), (?~)) import Control.Lens.Operators ((.~)) import Control.Monad.State (@@ -53,36 +119,26 @@ import Data.Default (Default (..)) import Data.Foldable (Foldable (..)) import Data.List (intercalate)-import qualified Data.Map.Strict as Map (Map, insert)+import Data.Map.Strict qualified as Map (Map, insert) import Data.Maybe (isJust, maybeToList)-import qualified Data.Text as T---- Coords+import Data.Text qualified as T  -- TODO Allow sheet addresses +-- TODO Make formulas aware of the current sheet+-- Or, make just formula printers aware so that they don't print the full address+-- when referring to data on the same sheet++{- FOURMOLU_DISABLE -}+-- $Coords+{- FOURMOLU_ENABLE -}+ -- | Coords of a cell data Coords = Coords {row :: Int, col :: Int}  instance Show Coords where   show :: Coords -> String-  show (Coords{..}) = toLetters col <> show row--alphabet :: [String]-alphabet = (: "") <$> ['A' .. 'Z']--toLetters :: Int -> String-toLetters x = f "" (x - 1)- where-  new :: Int -> String -> String-  new cur acc = alphabet !! (cur `mod` 26) <> acc-  f :: String -> Int -> String-  f acc cur = if cur `div` 26 > 0 then f (new cur acc) (cur `div` 26 - 1) else new cur acc--{-->>>toLetters <$> [1, 26, 27, 52, 78]-["A","Z","AA","AZ","BZ"]--}+  show (Coords{..}) = toAlphaNumeric col <> show row  instance Num Coords where   (+) :: Coords -> Coords -> Coords@@ -98,28 +154,97 @@   fromInteger :: Integer -> Coords   fromInteger x = Coords (fromIntegral (abs x)) (fromIntegral (abs x)) --- Cell+-- | Letters that can be used in column indices+alphabet :: [Char]+alphabet = ['A' .. 'Z'] +-- | Translate a number into an alphanumeric representation. Relevant for columns+toAlphaNumeric :: Int -> String+toAlphaNumeric x = f "" (x - 1)+ where+  new :: Int -> String -> String+  new cur acc = [alphabet !! (cur `mod` 26)] <> acc+  f :: String -> Int -> String+  f acc cur = if cur `div` 26 > 0 then f (new cur acc) (cur `div` 26 - 1) else new cur acc++{-+>>>toLetters <$> [1, 26, 27, 52, 78]+["A","Z","AA","AZ","BZ"]+-}++-- {- FOURMOLU_DISABLE -}++-- $CellRef+-- {\- FOURMOLU_ENABLE -\}++-- | A typed reference to a cell.+--+-- The user is responsible for setting the necessary cell type.+--+-- The type prevents operations between cell references with incompatible types.+--+-- >>>str = CellRef (Coords 1 1) :: CellRef String+-- >>> str |+| str+-- No instance for (Num String) arising from a use of ‘|+|’+--+-- When necessary, the user may change the cell reference type via 'unsafeChangeCellRefType'+--+-- >>>int = CellRef (Coords 1 1) :: CellRef Int+-- >>>double = CellRef (Coords 2 5) :: CellRef Double+-- >>>unsafeChangeCellRefType int |+| double+-- A1+E2+newtype CellRef a = CellRef {unCell :: Coords}+  deriving newtype (Num)++-- | Get a column number from a 'CellRef'+getCol :: CellRef a -> Int+getCol (CellRef c) = c & col++-- | Get a row number from a 'CellRef'+getRow :: CellRef a -> Int+getRow (CellRef c) = c & row++-- | Apply a function over a column of a coordinate+overCol :: (Int -> Int) -> Coords -> Coords+overCol f (Coords row col) = Coords row (f col)++-- | Apply a function over a row of a coordinate+overRow :: (Int -> Int) -> Coords -> Coords+overRow f (Coords row col) = Coords (f row) col++-- | Change the type of a cell reference. Use with caution!+--+-- The type variables in the @forall@ clause are swapped for the conveniece of type applications+unsafeChangeCellRefType :: forall b a. CellRef a -> CellRef b+unsafeChangeCellRefType (CellRef c) = CellRef c++{- FOURMOLU_DISABLE -}+-- $Formatting+{- FOURMOLU_ENABLE -}+ -- | Index of an input-type Index = Int+type InputIndex = Int  -- | Format a single cell depending on its coordinates, index, and data-type FormatCell = Coords -> Index -> CellData -> X.FormattedCell+type FormatCell = Coords -> InputIndex -> CellData -> X.FormattedCell --- | Cell with contents, style, column props+-- | Template of a cell with contents, style, column properties data CellTemplate input output = CellTemplate   { mkOutput :: input -> output-  , format :: FormatCell+  , fmtCell :: FormatCell   , columnsProperties :: Maybe X.ColumnsProperties   } --- Transforms-+-- | Map of coordinates to cell formatting type FormattedMap = Map.Map (X.RowIndex, X.ColumnIndex) X.FormattedCell++-- | Transform of a map that maps coordinates to cell formatting type FMTransform = FormattedMap -> FormattedMap++-- | Transform of a worksheet type WSTransform = X.Worksheet -> X.Worksheet --- | A transform of the map of formats and a transform of a worksheet+-- | Combined: a transform of a map of formats and a transform of a worksheet data Transform = Transform {fmTransform :: FMTransform, wsTransform :: WSTransform}  instance Semigroup Transform where@@ -134,36 +259,77 @@   def :: Transform   def = mempty --- Template+-- | Make a 'FormatCell' for a single color+--+-- @show@ on the input should translate into an @ARGB@ color. See 'XS.Color'+mkColorStyle :: Show a => a -> FormatCell+mkColorStyle color _ _ cd =+  X.def+    & X.formattedCell .~ dataCell cd+    & X.formattedFormat+      .~ ( X.def+            & X.formatFill+              ?~ ( X.def+                    & X.fillPattern+                      ?~ ( X.def+                            & ( X.fillPatternFgColor+                                  ?~ (X.def & X.colorARGB ?~ T.pack (show color))+                              )+                            & ( X.fillPatternType+                                  ?~ X.PatternTypeSolid+                              )+                         )+                 )+         ) +-- | Transform of a formatted cell+type FCTransform = X.FormattedCell -> X.FormattedCell++-- | Apply 'FCTransform' to a 'FormatCell' to get a new 'FormatCell'+(+>) :: FormatCell -> FCTransform -> FormatCell+fc +> ft = \coords idx cd -> ft $ fc coords idx cd++infixl 5 +>++-- | Get a 'FCTransform' with a given horizontal alignment in a cell+horizontalAlignment :: X.CellHorizontalAlignment -> FCTransform+horizontalAlignment alignment fc =+  fc+    & X.formattedFormat+      %~ ( \ff ->+            ff+              & X.formatAlignment+                ?~ ( X.def & X.alignmentHorizontal ?~ alignment+                   )+         )++{- FOURMOLU_DISABLE -}+-- $Templates+{- FOURMOLU_ENABLE -}+ -- | Template for multiple cells newtype Template input output = Template [CellTemplate input output]   deriving (Semigroup, Monoid) --- Builder---- | A builder-newtype Builder input output a = Builder {unBuilder :: StateT Coords (Writer (Template input output)) a}+-- | Allows to describe how to build a template for a row+newtype RowBuilder input output a = RowBuilder {unBuilder :: StateT Coords (Writer (Template input output)) a}   deriving (Functor, Applicative, Monad, MonadState Coords, MonadWriter (Template input output))  -- | Run builder on given coordinates. Get a result and a template-runBuilder :: Builder input output a -> Coords -> (a, Template input output)+runBuilder :: RowBuilder input output a -> Coords -> (a, Template input output) runBuilder builder coord = runWriter (evalStateT (unBuilder builder) coord)  -- | Run builder on given coordinates. Get a template-evalBuilder :: Builder input output a -> Coords -> Template input output+evalBuilder :: RowBuilder input output a -> Coords -> Template input output evalBuilder builder coord = snd $ runBuilder builder coord  -- | Run builder on given coordinates. Get a result-execBuilder :: Builder input output a -> Coords -> a+execBuilder :: RowBuilder input output a -> Coords -> a execBuilder builder coord = fst $ runBuilder builder coord -type RenderTemplate m input output = (Monad m, ToCellData output) => Coords -> Index -> input -> Template input output -> m Transform-type RenderBuilderInputs m input output a = (Monad m, ToCellData output) => Builder input output a -> [input] -> m (Transform, a)-type RenderBuilderInput m input output a = (Monad m, ToCellData output) => Builder input output a -> input -> m (Transform, a)---- Render--- meaning produce a transform+type RenderTemplate m input output = (Monad m, ToCellData output) => Coords -> InputIndex -> input -> Template input output -> m Transform+type RenderBuilderInputs m input output a = (Monad m, ToCellData output) => RowBuilder input output a -> [input] -> m (Transform, a)+type RenderBuilderInput m input output a = (Monad m, ToCellData output) => RowBuilder input output a -> input -> m (Transform, a)  -- | Render a builder with given coords and inputs. Return the result calculated using the topmost row renderBuilderInputs :: (Monad m, ToCellData output) => Coords -> RenderTemplate m input output -> RenderBuilderInputs m input output a@@ -202,7 +368,7 @@             cd' = toCellData (mkOutput input)             col' = (col + columnIdx)             coords' = Coords row col'-            c = format coords' inputIdx cd'+            c = fmtCell coords' inputIdx cd'             fmTransform = Map.insert (fromIntegral row, fromIntegral col') c             wsTransform               -- add column width only once@@ -214,8 +380,11 @@       [0 ..]       columns --- Columns+{- FOURMOLU_DISABLE -}+-- $Columns+{- FOURMOLU_ENABLE -} +-- | Properties of a column newtype ColumnsProperties = ColumnsProperties {unColumnsProperties :: X.ColumnsProperties}  instance Default ColumnsProperties where@@ -232,12 +401,9 @@         , cpBestFit = False         } --- TODO fix doesn't work for non-first row--- need to filter the final list---- | Produce a column with a given style and width and get a cell-columnWidthCell :: forall a input output. Maybe Double -> FormatCell -> (input -> output) -> Builder input output (Cell a)-columnWidthCell width format mkOutput = do+-- | A column with a possibly given width and cell format. Returns a cell reference+columnWidthCell :: forall a input output. Maybe Double -> FormatCell -> (input -> output) -> RowBuilder input output (CellRef a)+columnWidthCell width fmtCell mkOutput = do   coords <- get   let columnsProperties =         Just $@@ -246,100 +412,91 @@             , X.cpMax = coords & col             , X.cpWidth = width             }-  tell (Template [CellTemplate{format, mkOutput, columnsProperties}])-  cell <- gets Cell+  tell (Template [CellTemplate{fmtCell, mkOutput, columnsProperties}])+  cell <- gets CellRef   modify (\x -> x{col = (x & col) + 1})   return cell -columnWidth :: ToCellData output => Double -> FormatCell -> (input -> output) -> Builder input CellData (Cell a)+-- | A column with a given width and cell format. Returns a cell reference+columnWidth :: ToCellData output => Double -> FormatCell -> (input -> output) -> RowBuilder input CellData (CellRef a) columnWidth width fmtCell mkOutput = columnWidthCell (Just width) fmtCell (toCellData . mkOutput) -columnWidth_ :: ToCellData output => Double -> FormatCell -> (input -> output) -> Builder input CellData ()+-- | A column with a given width and cell format+columnWidth_ :: ToCellData output => Double -> FormatCell -> (input -> output) -> RowBuilder input CellData () columnWidth_ width fmtCell mkOutput = void (columnWidth width fmtCell mkOutput) --- | Produce a column with a given style and get a cell-column :: ToCellData output => FormatCell -> (input -> output) -> Builder input CellData (Cell a)+-- | A column with a given cell format. Returns a cell reference+column :: ToCellData output => FormatCell -> (input -> output) -> RowBuilder input CellData (CellRef a) column fmtCell mkOutput = columnWidthCell Nothing fmtCell (toCellData . mkOutput) --- | Produce a column with a given style-column_ :: ToCellData output => FormatCell -> (input -> output) -> Builder input CellData ()+-- | A column with a given cell format+column_ :: ToCellData output => FormatCell -> (input -> output) -> RowBuilder input CellData () column_ fmtCell mkOutput = void (column fmtCell mkOutput) --- | Produce a transform and a result from inputs and a builder-composeTransformResult :: forall a input output. ToCellData output => RenderTemplate Identity input output -> Coords -> [input] -> Builder input output a -> (Transform, a)-composeTransformResult renderTemplate' offset input builder = runIdentity $ renderBuilderInputs offset renderTemplate' builder input+-- | Produce a transform and a result from a template renderer, inputs, and a builder+composeTransformAndResult :: forall a input output. ToCellData output => RenderTemplate Identity input output -> Coords -> [input] -> RowBuilder input output a -> (Transform, a)+composeTransformAndResult renderTemplate' offset input builder = runIdentity $ renderBuilderInputs offset renderTemplate' builder input --- | Produce a result-defaultTransformResult :: ToCellData output => Coords -> [input] -> Builder input output a -> (Transform, a)-defaultTransformResult = composeTransformResult renderTemplate+-- | Produce a result from a default template renderer, inputs, and a builder+defaultComposeTransformAndResult :: ToCellData output => Coords -> [input] -> RowBuilder input output a -> (Transform, a)+defaultComposeTransformAndResult = composeTransformAndResult renderTemplate --- TODO--- Store current sheet info for formulas+{- FOURMOLU_DISABLE -}+-- $SheetBuilder+{- FOURMOLU_ENABLE -} --- | Top monad to compose the results of Builders+-- | A builder to compose the results of 'RowBuilder's newtype SheetBuilder a = SheetBuilder {unSheetBuilder :: Writer Transform a}   deriving (Functor, Applicative, Monad, MonadWriter Transform) -class Functor a => Discardable a where-  discard :: a b -> a ()--placeInputs :: ToCellData output => Coords -> [input] -> Builder input output a -> SheetBuilder a+-- | Starting at given coordinates, place rows of data made from a list of inputs according to a row builder. Return the result of the row builder.+placeInputs :: ToCellData output => Coords -> [input] -> RowBuilder input output a -> SheetBuilder a placeInputs offset inputs b = do-  let transformResult = defaultTransformResult offset inputs b+  let transformResult = defaultComposeTransformAndResult offset inputs b   tell (fst transformResult)   return (snd transformResult) -placeInput :: ToCellData output => Coords -> input -> Builder input output a -> SheetBuilder a+-- | Starting at given coordinates, place a row of data made from a single input according to a row builder. Return the result of the row builder.+placeInput :: ToCellData output => Coords -> input -> RowBuilder input output a -> SheetBuilder a placeInput coords input = placeInputs coords [input] -placeInputs_ :: ToCellData output => Coords -> [input] -> Builder input output a -> SheetBuilder ()+-- | Starting at given coordinates, place rows of data made from a list of inputs according to a row builder.+placeInputs_ :: ToCellData output => Coords -> [input] -> RowBuilder input output a -> SheetBuilder () placeInputs_ coords inputs b = void (placeInputs coords inputs b) -placeInput_ :: ToCellData output => Coords -> input -> Builder input output a -> SheetBuilder ()+-- | Starting at given coordinates, place a row of data made from a single input according to a row builder.+placeInput_ :: ToCellData output => Coords -> input -> RowBuilder input output a -> SheetBuilder () placeInput_ coords input = placeInputs_ coords [input] -composeXlsx :: [(T.Text, SheetBuilder ())] -> X.Xlsx-composeXlsx sheetBuilders = workBook'- where-  getTransform x = execWriter $ unSheetBuilder x-  workBook = X.formatWorkbook ((\(name, tf') -> (name, (getTransform tf' & fmTransform) X.def)) <$> sheetBuilders) X.def-  filterWidths ws = ws & X.wsColumnsProperties %~ filter (isJust . X.cpWidth)-  workBook' =-    workBook-      & X.xlSheets-        %~ \sheets -> zipWith (\x (name, ws) -> (name, (getTransform x & wsTransform) ws & filterWidths)) (snd <$> sheetBuilders) sheets--{- Lib. Formulas -}+{- FOURMOLU_DISABLE -}+-- $Expressions+{- FOURMOLU_ENABLE -} --- | Formula expressions+-- | Expression syntax data Expr t   = Add (Expr t) (Expr t)   | Sub (Expr t) (Expr t)   | Mul (Expr t) (Expr t)   | Div (Expr t) (Expr t)+  | Power (Expr t) (Expr t)   | Function String [Expr t]   | Range (Expr t) (Expr t)-  | ExprCell (Cell t)-  deriving (Functor)---- | Change phantom type of an Expr-ex' :: forall b a. Expr a -> Expr b-ex' = toExpr+  | ExprCell (CellRef t)  -- | Something that can be turned into an expression class ToExpr v where   toExpr :: v -> Expr t -instance ToExpr (Cell a) where-  toExpr :: Cell a -> Expr t-  toExpr (Cell c) = ExprCell (Cell c)+instance ToExpr (CellRef a) where+  toExpr :: CellRef a -> Expr t+  toExpr (CellRef c) = ExprCell (CellRef c)  instance ToExpr Coords where   toExpr :: Coords -> Expr t-  toExpr c = ExprCell (Cell c)+  toExpr c = ExprCell (CellRef c) -toExprCell :: Cell a -> Coords-toExprCell (Cell c1) = c1+toExprCell :: CellRef a -> Coords+toExprCell (CellRef c1) = c1  instance ToExpr (Expr a) where   toExpr :: Expr a -> Expr b@@ -347,9 +504,10 @@   toExpr (Sub l r) = Sub (toExpr l) (toExpr r)   toExpr (Mul l r) = Mul (toExpr l) (toExpr r)   toExpr (Div l r) = Div (toExpr l) (toExpr r)+  toExpr (Power b p) = Power (toExpr b) (toExpr p)   toExpr (Function name args) = Function name (toExpr <$> args)   toExpr (Range l r) = Range (toExpr l) (toExpr r)-  toExpr (ExprCell (Cell c)) = ExprCell (Cell c)+  toExpr (ExprCell (CellRef c)) = ExprCell (CellRef c)  showOp2 :: (Show a, Show b) => String -> a -> b -> String showOp2 operator c1 c2 = show c1 <> operator <> show c2@@ -361,38 +519,41 @@ mkNumOp2 = mkOp2  -- | Assemble a range expression-(|:|) :: Cell a -> Cell b -> Expr c+(|:|) :: CellRef a -> CellRef b -> Expr c (|:|) = mkOp2 Range  infixr 5 |:| +-- | A type for arithmetic operators+type ArithmeticOperator a b c = (Num a, ToExpr (b a), ToExpr (c a)) => b a -> c a -> Expr a+ -- | Assemble an addition expression-(|+|) :: Num a => Expr a -> Expr a -> Expr a+(|+|) :: ArithmeticOperator a b c (|+|) = mkNumOp2 Add  infixl 6 |+|  -- | Assemble a subtraction expression-(|-|) :: Num a => Expr a -> Expr a -> Expr a+(|-|) :: ArithmeticOperator a b c (|-|) = mkNumOp2 Sub  infixl 6 |-|  -- | Assemble a division expression-(|/|) :: Num a => Expr a -> Expr a -> Expr a+(|/|) :: ArithmeticOperator a b c (|/|) = mkNumOp2 Div  infixl 7 |/|  -- | Assemble a multiplication expression-(|*|) :: Num a => Expr a -> Expr a -> Expr a+(|*|) :: ArithmeticOperator a b c (|*|) = mkNumOp2 Mul  infixl 6 |*|  -- | Assemble a multiplication expression-(|^|) :: Num a => Expr a -> Expr a -> Expr a-(|^|) = mkNumOp2 Mul+(|^|) :: ArithmeticOperator a b c+(|^|) = mkNumOp2 Power  infixr 8 |^| @@ -408,100 +569,22 @@   show (Sub c1 c2) = showOp2 "-" c1 c2   show (Mul c1 c2) = showOp2 "*" c1 c2   show (Div c1 c2) = showOp2 "/" c1 c2+  show (Power c1 c2) = showOp2 "^" c1 c2   show (Range c1 c2) = showOp2 ":" c1 c2-  show (ExprCell (Cell e)) = show e+  show (ExprCell (CellRef e)) = show e   show (Function n as) = n <> "(" <> intercalate "," (show <$> as) <> ")" --- | Coordinates of a cell with a given phantom type-newtype Cell a = Cell {unCell :: Coords} deriving (Functor)--cellCol :: Cell a -> Int-cellCol (Cell c) = c & col--cellRow :: Cell a -> Int-cellRow (Cell c) = c & row--overCol :: (Int -> Int) -> Coords -> Coords-overCol f (Coords row col) = Coords row (f col)--overRow :: (Int -> Int) -> Coords -> Coords-overRow f (Coords row col) = Coords (f row) col--instance Num (Cell a) where-  (+) :: Cell a -> Cell a -> Cell a-  (+) (Cell c1) (Cell c2) = Cell (c1 + c2)-  (*) :: Cell a -> Cell a -> Cell a-  (*) (Cell c1) (Cell c2) = Cell (c1 * c2)-  (-) :: Cell a -> Cell a -> Cell a-  (-) (Cell c1) (Cell c2) = Cell (c1 - c2)-  abs :: Cell a -> Cell a-  abs (Cell c1) = Cell (abs c1)-  signum :: Cell a -> Cell a-  signum (Cell c1) = Cell (signum c1)-  fromInteger :: Integer -> Cell a-  fromInteger x = Cell (fromInteger x)---- | Convert a typed cell to an expression-ex :: Cell a -> Expr a-ex = toExpr--{- Lib.Example.Typechecks->>>str = ex (Cell (Coords 1 1)) :: Expr String->>> str |+| str-No instance for (Num String) arising from a use of `|+|'-In the expression: str |+| str-NOW In an equation for `it_a1TViD': it_a1TViD = str |+| str-->>>int = ex (Cell (Coords 1 1)) :: Expr Int->>>double = ex (Cell (Coords 2 5)) :: Expr Double->>>ex' int |+| double-A1+E2--}--mkColorStyle :: T.Text -> FormatCell-mkColorStyle color _ _ cd =-  X.def-    & X.formattedCell .~ dataCell cd-    & X.formattedFormat-      .~ ( X.def-            & X.formatFill-              ?~ ( X.def-                    & X.fillPattern-                      ?~ ( X.def-                            & ( X.fillPatternFgColor-                                  ?~ (X.def & X.colorARGB ?~ color)-                              )-                            & ( X.fillPatternType-                                  ?~ X.PatternTypeSolid-                              )-                         )-                 )-         )--type FCTransform = X.FormattedCell -> X.FormattedCell--infixl 5 <|-(<|) :: FCTransform -> FormatCell -> FormatCell-f <| fc = \coords idx cd -> f $ fc coords idx cd--horizontalAlignment :: X.CellHorizontalAlignment -> FCTransform-horizontalAlignment alignment fc =-  fc-    & X.formattedFormat-      %~ ( \ff ->-            ff-              & X.formatAlignment-                ?~ ( X.def & X.alignmentHorizontal ?~ alignment-                   )-         )+{- FOURMOLU_DISABLE -}+-- $Cells+{- FOURMOLU_ENABLE -} --- | A union of some Cell components+-- | A union of what can be inside a cell data CellData   = CellFormula X.CellFormula   | CellValue X.CellValue   | CellComment X.Comment --- | Convert some Cell component into a cell+-- | Convert some CellRef component into a cell dataCell :: CellData -> X.Cell dataCell cd =   X.def@@ -510,6 +593,7 @@       CellFormula d -> X.cellFormula ?~ d       CellComment d -> X.cellComment ?~ d +-- | Something that can be turned into 'CellData' class ToCellData a where   toCellData :: a -> CellData @@ -542,3 +626,19 @@         , X._cellfCalculate = True         , X._cellfExpression = X.NormalFormula $ X.Formula $ T.pack $ show e         }++{- FOURMOLU_DISABLE -}+-- $Xlsx+{- FOURMOLU_ENABLE -}++-- | Compose an @xlsx@ from a list of sheet names and builders+composeXlsx :: [(T.Text, SheetBuilder ())] -> X.Xlsx+composeXlsx sheetBuilders = workBook'+ where+  getTransform x = execWriter $ unSheetBuilder x+  workBook = X.formatWorkbook ((\(name, tf') -> (name, (getTransform tf' & fmTransform) X.def)) <$> sheetBuilders) X.def+  filterWidths ws = ws & X.wsColumnsProperties %~ filter (isJust . X.cpWidth)+  workBook' =+    workBook+      & X.xlSheets+        %~ \sheets -> zipWith (\x (name, ws) -> (name, (getTransform x & wsTransform) ws & filterWidths)) (snd <$> sheetBuilders) sheets
+ test/Docs.hs view
@@ -0,0 +1,33 @@+{-# LANGUAGE ConstraintKinds #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE MultiParamTypeClasses #-}+{-# LANGUAGE UndecidableInstances #-}+{-# OPTIONS_GHC -Wno-unused-top-binds #-}++{- FOURMOLU_ENABLE -}++module Main (main) where++import Control.Monad (when)+import GHC.IO.Exception (ExitCode (..))+import System.Exit (exitFailure, exitSuccess)+import System.Process.Typed (runProcess, shell)++main :: IO ()+main = do+  print "Converting README"+  s1 <-+    runProcess $+      shell $+        unlines+          [ "lima hs2md -f example/app/Main.hs"+          , "touch README.tmp"+          , "cat README/Intro.md >> README.tmp"+          , "printf \"\n\" >> README.tmp"+          , "cat example/app/Main.hs.md >> README.tmp && rm example/app/Main.hs.md"+          , "printf \"\n\" >> README.tmp"+          , "cat README/Outro.md >> README.tmp"+          , "mv README.tmp README.md"+          ]+  when (s1 /= ExitSuccess) (print "Failed to generate README.md. Exiting ..." >> exitFailure)+  exitSuccess