{"id":19842876,"url":"https://github.com/deemp/clerk","last_synced_at":"2025-05-01T20:31:57.607Z","repository":{"id":65138312,"uuid":"582381748","full_name":"deemp/clerk","owner":"deemp","description":"Declaratively generate spreadsheets","archived":false,"fork":false,"pushed_at":"2023-07-05T00:51:22.000Z","size":14820,"stargazers_count":4,"open_issues_count":8,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-06T17:23:32.631Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Haskell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/deemp.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-12-26T16:42:25.000Z","updated_at":"2024-10-25T03:55:00.000Z","dependencies_parsed_at":"2024-11-12T12:40:10.580Z","dependency_job_id":"81a7e458-3ef5-4450-bb19-9f5db52eccc1","html_url":"https://github.com/deemp/clerk","commit_stats":{"total_commits":93,"total_committers":2,"mean_commits":46.5,"dds":0.3870967741935484,"last_synced_commit":"595fdf519e7f50467283951c30472c7c2684a0cb"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/deemp%2Fclerk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/deemp%2Fclerk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/deemp%2Fclerk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/deemp%2Fclerk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/deemp","download_url":"https://codeload.github.com/deemp/clerk/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251940875,"owners_count":21668627,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-12T12:35:55.012Z","updated_at":"2025-05-01T20:31:52.825Z","avatar_url":"https://github.com/deemp.png","language":"Haskell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# clerk\n\n`clerk` provides a Haskell 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.\n\n## Features\n\n`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.\n\nThe library supports:\n\n- Typed cell references. Example: `Ref Double`.\n- Type-safe arithmetic operations with them. Example: `(a :: Ref Double) + (b :: Ref Double)` produces a `Ref Double`.\n- 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`).\n- Conditional styles, formatting, column widths.\n\nThe examples below demonstrate these features.\n\n\u003c!-- FOURMOLU_DISABLE --\u003e\n\n## Example 1\n\n**The goal**: demonstrate formula syntax.\n\nThe source code for this example is available [here](app/Example1.hs).\n\n### Extensions\n\n```haskell\n{-# LANGUAGE OverloadedStrings #-}\n{-# LANGUAGE TypeApplications #-}\n{-# LANGUAGE GADTs #-}\n{-# LANGUAGE DataKinds #-}\n{-# LANGUAGE ScopedTypeVariables #-}\n```\n\n### Imports\n\nI import the necessary stuff.\n\n```haskell\nimport Clerk ( (.*), (.**), (.+), (./), as, fun, formulaRef,  val, Formula, Ref )\nimport Data.Text (Text)\nimport Examples.Helpers ( showFormula, mkRef )\n```\n\n\u003c!-- LIMA_DISABLE\n\nmain :: IO ()\nmain = putStrLn \"Hello, World!\"\n\nLIMA_ENABLE --\u003e\n\n### Formulas\n\nFormulas consist of references, functions, and values.\n\nI make references to `Double` values\n\n```haskell\nr1 :: Ref Double\nr1 = mkRef @\"B4\"\nr2 :: Ref Double\nr2 = mkRef @\"E6\"\nr3 :: Ref Double\nr3 = mkRef @\"G8\"\n```\n\nNext, I convert one of these references to a formula via `formulaRef` and inspect the formula representation.\n\n```haskell\nt1 :: Text\nt1 = showFormula $ formulaRef r2\n\n-- \u003e\u003e\u003et1\n-- \"E6\"\n```\n\nFinally, I construct a longer expression and look at its representation.\nI convert a literal value to a formula via `val`.\n\n```haskell\nt2 :: Text\nt2 = showFormula $ r1 .* r2 .* val 3 .+ r1 .** r2 ./ r3\n\n-- \u003e\u003e\u003et2\n-- \"B4*E6*3.0+B4^E6/G8\"\n```\n\nOf course, I can mix differently typed references in expressions when necessary.\nFor this case, I have an unsafe `as` function.\n\n```haskell\nr4 :: Ref Int\nr4 = mkRef @\"T6\"\n\nt3 :: Text\nt3 = showFormula $ as @Double (r4 .* r4) .+ r1 .** r2 ./ r3\n\n-- \u003e\u003e\u003et3\n-- \"T6*T6+B4^E6/G8\"\n```\n\nThis `as` function should not be abused, though. If I need an `Int` instead of a `Double`, I can explicitly use an Excel function.\n\n```haskell\nround_ :: forall a. Formula a -\u003e Formula Int\nround_ x = fun \"ROUND\" [x]\n\nt4 :: Formula Int\nt4 = round_ (r1 .** r2 ./ r3)\n\n-- \u003e\u003e\u003e:t t4\n-- t4 :: Formula Int\n\nt5 :: Text\nt5 = showFormula t4\n\n-- \u003e\u003e\u003e t5\n-- \"ROUND(B4^E6/G8)\"\n```\n\n\u003c!-- FOURMOLU_DISABLE --\u003e\n\n## Example 2\n\n**The goal**: describe and generate a spreadsheet with a simple multiplication table.\n\nThe source code for this example is available [here](app/Example2.hs).\n\nThe program produces an `xlsx` file that looks as follows:\n\n\u003cimg src = \"https://raw.githubusercontent.com/deemp/clerk/master/README/Example2/demoValues.png\" width = \"80%\"\u003e\n\nWith formulas enabled:\n\n\u003cimg src = \"https://raw.githubusercontent.com/deemp/clerk/master/README/Example2/demoFormulas.png\" width = \"80%\"\u003e\n\nThe below sections describe how such a spreadsheet can be constructed.\n\n### Extensions\n\nI'll need several language extensions.\n\n\u003c!-- LIMA_DISABLE\n\n{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}\n{-# HLINT ignore \"Redundant bracket\" #-}\n{-# LANGUAGE TypeApplications #-}\n{-# LANGUAGE DataKinds #-}\n\nLIMA_ENABLE --\u003e\n\n\u003c!-- FOURMOLU_ENABLE --\u003e\n\n### Imports\n\nI import the necessary stuff.\n\n```haskell\nimport Clerk\nimport Control.Monad (forM, forM_, void)\nimport qualified Data.Text as T\nimport Lens.Micro ((\u0026), (+~), (^.))\n```\n\n### Tables\n\nThe tables that I'd like to construct are:\n\n- A vertical header\n- A horizontal header\n- A table with results of multiplication of the numbers from these headers\n\n#### A vertical header\n\n\u003cimg src = \"https://raw.githubusercontent.com/deemp/clerk/master/README/Example2/vertical.png\" width = \"10%\"\u003e\n\n`clerk` provides the `RowI` monad.\nThis monad takes some `i`nput, internally converts it into Excel types, and outputs something, e.g., a cell reference.\nIn background, it writes a template of a horizontal block of cells - a **row**.\nThis row is used for placing the input values onto a sheet.\n\nA vertical block of cells can be represented as several horizontal blocks of cells placed under each other.\nSo, as a template, I use a `RowI` with one integer as an input.\n\nAs I don't need any formatting, I use `blank` cells for templates.\n\nI place the rows for each input value and collect the references.\nEach row is shifted relative to the input coordinates.\n\n```haskell\nmkVertical :: Coords -\u003e [Int] -\u003e Sheet [Ref Int]\nmkVertical coords numbers =\n  forM (zip [0 ..] numbers) $ \\(idx, number) -\u003e\n    placeIn\n      (coords \u0026 row +~ idx + 2)\n      number\n      ((columnF blank (const number)) :: RowI Int (Ref Int))\n```\n\n#### A horizontal header\n\n\u003cimg src = \"https://raw.githubusercontent.com/deemp/clerk/master/README/Example2/horizontal.png\" width = \"80%\"\u003e\n\nFor a horizontal header, I make a row of numbers and collect the references to all its cells.\nAs I don't care about the type of inputs, I use the `Row` type.\n\nIn the `Sheet` monad, I place this row starting at a specified coordinate.\n\n```haskell\nmkHorizontal :: Coords -\u003e [Int] -\u003e Sheet [Ref Int]\nmkHorizontal coords numbers =\n  place\n    (coords \u0026 col +~ 2)\n    ((forM numbers $ \\n -\u003e columnF blank (const n)) :: Row [Ref Int])\n```\n\n#### Table builder\n\n\u003cimg src = \"https://raw.githubusercontent.com/deemp/clerk/master/README/Example2/table.png\" width = \"50%\"\u003e\n\nFor inner cells, I use single-cell rows for each input.\nAs I don't need any info about these cells, I use the `Row ()` type.\n\n```haskell\nmkTable :: [(Ref Int, Ref Int)] -\u003e Sheet ()\nmkTable cs =\n  forM_ cs $ \\(r, c) -\u003e do\n    coords \u003c- mkCoords' (c ^. col) (r ^. row)\n    place coords ((columnF_ blank (const (r .* c))) :: Row ())\n```\n\n### Sheet\n\nNow, I combine all functions.\n\n```haskell\nsheet :: Sheet ()\nsheet = do\n  start \u003c- mkCoords @\"B2\"\n  let numbers = [1 .. 9]\n  cs \u003c- mkHorizontal start numbers\n  rs \u003c- mkVertical start numbers\n  mkTable [(r, c) | r \u003c- rs, c \u003c- cs]\n```\n\n### Result\n\nFinally, I can write the result and get a spreadsheet like the one at the beginning of [Example 2](#example-2).\n\n```haskell\nmain :: IO ()\nmain = writeXlsx \"example2.xlsx\" [(T.pack \"List 1\", void sheet)]\n```\n\nTo get `./example2.xlsx`, run:\n\n```console\nnix run .#example2\n-- or\ncabal run example2\n```\n\nWith formulas enabled, the sheet looks like this:\n\n\u003cimg src = \"https://raw.githubusercontent.com/deemp/clerk/master/README/Example2/demoFormulas.png\" width = \"80%\"\u003e\n\n\u003c!-- FOURMOLU_DISABLE --\u003e\n\n## Example 3\n\n**The goal**: describe and generate a spreadsheet that calculates the pressure data given some volume data and constants.\n\nThe source code for this example is available [here](app/Example3.hs).\n\nThe program produces an `xlsx` file that looks as follows:\n\n\u003cimg src = \"https://raw.githubusercontent.com/deemp/clerk/master/README/Example3/demoValues.png\" width = \"80%\"\u003e\n\nWith formulas enabled:l\n\n\u003cimg src = \"https://raw.githubusercontent.com/deemp/clerk/master/README/Example3/demoFormulas.png\" width = \"80%\"\u003e\n\nThe below sections describe how such a spreadsheet can be constructed.\n\n### Extensions\n\nI'll need several language extensions.\n\n```haskell\n-- to access the fields of records like a.b\n{-# LANGUAGE OverloadedRecordDot #-}\n{-# LANGUAGE ImportQualifiedPost #-}\n{-# LANGUAGE RankNTypes #-}\n{-# LANGUAGE RecordWildCards #-}\n{-# LANGUAGE DuplicateRecordFields #-}\n{-# LANGUAGE ScopedTypeVariables #-}\n{-# LANGUAGE NoOverloadedStrings #-}\n{-# LANGUAGE TypeApplications #-}\n{-# LANGUAGE DataKinds #-}\n```\n\n\u003c!-- FOURMOLU_ENABLE --\u003e\n\n### Imports\n\nAnd import the necessary stuff.\n\n```haskell\nimport Clerk\nimport Codec.Xlsx qualified as X\nimport Codec.Xlsx.Formatted qualified as X\nimport Data.Text qualified as T\nimport Lens.Micro ((%~), (\u0026), (+~), (?~))\n```\n\n### Tables\n\nThe tables that I'd like to construct are:\n\n- A table per a constant's value (three of them)\n- A volume and pressure table\n- A constants' header\n- A volume and pressure header\n\n#### constants values\n\n\u003cimg src = \"https://raw.githubusercontent.com/deemp/clerk/master/README/Example3/constants.png\" width = \"50%\"\u003e\n\nIn our case, each constant has the same type of the numeric value - `Double`.\nHowever, it might be the case that in another set of constants, they'll have different types.\nThat's why, I'll construct a table with a single row per a constant and later place the constants' tables under each other.\nI'll store constant data in a record.\n\n```haskell\ndata ConstantData a = ConstantData\n  { constantName :: String\n  , constantSymbol :: String\n  , constantValue :: a\n  , constantUnits :: String\n  }\n```\n\nNext, I group the constants.\n\n```haskell\ndata Constants f = Constants\n  { gasConstant :: f Double\n  , numberOfMoles :: f Double\n  , temperature :: f Double\n  }\n\ntype ConstantsInput = Constants ConstantData\n```\n\nFollowing that, I record the constants data.\n\n```haskell\nconstants :: ConstantsInput\nconstants =\n  Constants\n    { gasConstant = ConstantData \"GAS CONSTANT\" \"R\" 0.08206 \"L.atm/mol.K\"\n    , numberOfMoles = ConstantData \"NUMBER OF MOLES\" \"n\" 1 \"moles\"\n    , temperature = ConstantData \"TEMPERATURE(K)\" \"T\" 273.2 \"K\"\n    }\n```\n\nNow, I can make a `RowI` for a constant input.\nI use a `RowI` because this row cares about the `i`nput type.\nI'll later use this row for each constant separately.\n\nI get a pair of outputs:\n\n- Top left cell of a constant's table. That is, the cell with that constant's name.\n- The value of the constant.\n\nLater, I'll use these outputs to relate the positions of tables on a sheet.\n\nNotice that I use styles like `lightBlue` here. These styles are defined in the [Styles](#styles) section.\n\n```haskell\nconstant :: (ToCellData a) =\u003e RowI (ConstantData a) (Ref (), Ref a)\nconstant = do\n  refTopLeft \u003c- columnF lightBlue constantName\n  columnF_ lightBlue constantSymbol\n  refValue \u003c- columnF (lightBlue .\u0026 with2decimalDigits) constantValue\n  columnF_ lightBlue constantUnits\n  return (refTopLeft, refValue)\n```\n\n#### volume and pressure values\n\n\u003cimg src = \"https://raw.githubusercontent.com/deemp/clerk/master/README/Example3/valuesFormulas.png\" width = \"50%\"\u003e\n\nTo fill this table, I'll take some data and combine it with the constants.\n\n```haskell\nnewtype Volume = Volume {volume :: Double}\n\nvolumeData :: [Volume]\nvolumeData = Volume \u003c$\u003e [1 .. 10]\n```\n\nTo pass the constants references in a structured way, I make a helper type.\n\n```haskell\ntype ConstantsRefs = Constants Ref\n```\n\nNext, I define a function to produce a row for volume and pressure.\n\n```haskell\nvalues :: ConstantsRefs -\u003e RowI Volume ()\nvalues Constants{..} = do\n  refVolume \u003c- columnF alternatingColors volume\n  let pressure' = gasConstant .* numberOfMoles .* temperature ./ refVolume\n  columnF_ (alternatingColors .\u0026 with2decimalDigits) (const pressure')\n```\n\n#### Constants' header\n\n\u003cimg src = \"https://raw.githubusercontent.com/deemp/clerk/master/README/Example3/constantsHeader.png\" width = \"50%\"\u003e\n\nI won't use records here. Instead, I'll put the names of the columns straight into the `Row`.\n\nThe outputs will be the coordinates of the top left cell and the top right cell of this table.\n\n```haskell\nconstantsHeader :: Row (Ref (), Ref ())\nconstantsHeader = do\n  let style :: FormatCell\n      style = blue .\u0026 alignedCenter\n  refTopLeft \u003c- columnWF 20 style (const \"constant\")\n  columnWF_ 8 style (const \"symbol\")\n  columnF_ style (const \"value\")\n  refTopRight \u003c- columnWF 13 style (const \"units\")\n  return (refTopLeft, refTopRight)\n```\n\n#### Volume \u0026 Pressure header\n\n\u003cimg src = \"https://raw.githubusercontent.com/deemp/clerk/master/README/Example3/valuesHeader.png\" width = \"50%\"\u003e\n\nFor this header, I'll also put the names of columns straight into a row.\n\n```haskell\nvaluesHeader :: Row (Ref ())\nvaluesHeader = do\n  refTopLeft \u003c- columnWF 12 green (const \"VOLUME (L)\")\n  columnWF_ 16 green (const \"PRESSURE (atm)\")\n  return refTopLeft\n```\n\n### Sheet builder\n\nAt last, I combine all rows.\n\n```haskell\nsheet :: Sheet ()\nsheet = do\n  start \u003c- mkCoords @\"B2\"\n  (constantsHeaderTL, constantsHeaderTopRight) \u003c- place start constantsHeader\n  (gasTopLeft, gas) \u003c- placeIn (constantsHeaderTL \u0026 row +~ 2) constants.gasConstant constant\n  (nMolesTopLeft, nMoles) \u003c- placeIn (gasTopLeft \u0026 row +~ 1) constants.numberOfMoles constant\n  temperature \u003c- snd \u003c$\u003e placeIn (nMolesTopLeft \u0026 row +~ 1) constants.temperature constant\n  valuesHeaderTopLeft \u003c- place (constantsHeaderTopRight \u0026 col +~ 2) valuesHeader\n  placeIns (valuesHeaderTopLeft \u0026 row +~ 2) volumeData (values $ Constants gas nMoles temperature)\n```\n\n### Styles\n\nI used several styles to format the tables. This is how these styles are defined.\n\n```haskell\nblue, lightBlue, green, lightGreen :: FormatCell\nblue = mkColor (hex @\"#FF99CCFF\")\nlightBlue = mkColor (hex @\"#90CCFFFF\")\ngreen = mkColor (hex @\"#FF00FF00\")\nlightGreen = mkColor (hex @\"#90CCFFCC\")\n\nalternatingColors :: FormatCell\nalternatingColors index = (if even index then lightGreen else lightBlue) index\n```\n\nAdditionally, I compose an `FCTransform` for the number format.\nSuch a transform is used to accumulate cell formatting.\n\n```haskell\nwith2decimalDigits :: FCTransform\nwith2decimalDigits fcTransform =\n  fcTransform \u0026 X.formattedFormat %~ X.formatNumberFormat ?~ X.StdNumberFormat X.Nf2Decimal\n```\n\nAnd I make a transform for centering the cell content.\n\n```haskell\nalignedCenter :: FCTransform\nalignedCenter = horizontalAlignment X.CellHorizontalAlignmentCenter\n```\n\n### Result\n\nFinally, I write the result and get the spreadsheet like the one at the beginning of [Example 3](#example-3).\n\n```haskell\nmain :: IO ()\nmain = writeXlsx \"example3.xlsx\" [(T.pack \"List 1\", sheet)]\n```\n\nTo get `./example3.xlsx`, run:\n\n```console\nnix run .#example3\n-- or\ncabal run example3\n```\n\nWith formulas enabled, the sheet looks like this:\n\n\u003cimg src = \"https://raw.githubusercontent.com/deemp/clerk/master/README/Example3/demoFormulas.png\" width = \"80%\"\u003e\n\n```haskell\n{-# LANGUAGE DataKinds #-}\n{-# LANGUAGE OverloadedStrings #-}\n{-# LANGUAGE TypeApplications #-}\n\nimport Clerk\nimport Control.Monad (void)\nimport Control.Monad.RWS (gets)\n\nmain :: IO ()\nmain = writeXlsx \"example4.xlsx\" [(\"List 1\", sheet 9 15)]\n\ncolFormula :: ToCellData output =\u003e output -\u003e RowI input (Ref a)\ncolFormula = columnF blank . const\n\ncolIndex :: InputIndex -\u003e RowI input ()\ncolIndex = void . colFormula\n\nindex :: Row InputIndex\nindex = gets ((+ 1) . _inputIndex)\n\nrow0 :: Int -\u003e Int -\u003e Row (Ref Int, Ref Int)\nrow0 a b = do\n  colIndex =\u003c\u003c index\n  r1 \u003c- colFormula a\n  r2 \u003c- colFormula b\n  pure (r1, r2)\n\nrow1 :: (Ref Int, Ref Int) -\u003e Row (Ref Int, Ref Int)\nrow1 (a, b) =\n  do\n    colIndex =\u003c\u003c index\n    r1 \u003c- colFormula (fun \"MAX\" [a, b] :: Formula Int)\n    r2 \u003c- colFormula (fun \"MIN\" [a, b] :: Formula Int)\n    pure (r1, r2)\n\nrow3 :: (Ref Int, Ref Int) -\u003e Row (Ref Int, Ref Int)\nrow3 (a, b) = do\n  colIndex =\u003c\u003c index\n  r1 \u003c- colFormula (formulaRef a)\n  r2 \u003c- colFormula (formulaRef b)\n  r3 \u003c- colFormula (fun \"MOD\" [r1, r2] :: Formula Int)\n  pure (r2, r3)\n\nsheet :: Int -\u003e Int -\u003e Sheet ()\nsheet a b = do\n  start \u003c- mkRef @\"A1\"\n  s1 \u003c- place start (row0 a b)\n  placeIxsFs_ start [1 :: Int .. 6] (cycle [row1, row3]) s1\n  pure ()\n```\n\n## Contribute\n\nThis project provides a dev environment via a `Nix` flake.\n\n1. With [flakes enabled](https://nixos.wiki/wiki/Flakes#Enable_flakes), run:\n\n    ```console\n    nix develop\n    cabal build\n    ```\n\n1. This `README.md` is generated from several files. If you edit them, re-generate it.\n\n    ```console\n    cabal test docs\n    ```\n\n1. (Optionally) Start `VSCodium` with `Haskell` extensions.\n\n    1. Write settings and run `VSCodium`.\n\n        ```console\n        nix run .#writeSettings\n        nix run .#codium .\n        ```\n\n    1. Open a `Haskell` file. `Haskell Language Server` should soon start giving you hints.\n\n1. Study these links if you'd like to learn more about the tools used in this flake:\n\n    - [Prerequisites](https://github.com/deemp/flakes#prerequisites)\n    - `Haskell` project [template](https://github.com/deemp/flakes/tree/main/templates/codium/haskell#readme)\n    - [Haskell](https://github.com/deemp/flakes/blob/main/README/Haskell.md)\n\n1. If `Haskell Language Server` doesn't want to run code in `-- \u003e\u003e\u003e` comments:\n   1. Check the Output of `HLS`.\n   1. Find there a directory name containing `hie-bios`.\n   1. Remove the `hie-bios/dist-clerk-*` directory.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdeemp%2Fclerk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdeemp%2Fclerk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdeemp%2Fclerk/lists"}