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 +182/−182
- clerk.cabal +39/−29
- src/Clerk.hs +314/−214
- test/Docs.hs +33/−0
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