packages feed

cabal-fix-0.0.0.1: readme.org

* cabal-fix

[[https://hackage.haskell.org/package/cabal-fixes][https://img.shields.io/hackage/v/cabal-fix.svg]]
[[https://github.com/tonyday567/cabal-fixes/actions?query=workflow%3Ahaskell-ci][https://github.com/tonyday567/cabal-fix/workflows/haskell-ci/badge.svg]]

~cabal-fix~ helps fix your cabal files. This package:

- Contains an app which parses your existing cabal file, re-renders it according to some config, then either displays a diff or alters your cabal file, in-place.
- Is an idempotent parser of a cabal file or ByteString, into the ~Field~ type used in Cabal.
- Is an inexact printer.
- Contains code to explore the cabal index archive at the base of your cabal installation.

* App Usage

#+begin_src sh :results output :exports both

#+end_export
cabal-fix --help
#+end_src

#+RESULTS:
#+begin_example
fixes your cabal file

Usage: cabal-fix COMMAND [-d|--directory ARG] [-c|--config ARG]

  cabal fixer

Available options:
  -d,--directory ARG       project directory
  -c,--config ARG          config file
  -h,--help                Show this help text

Available commands:
  inplace                  fix cabal file inplace
  check                    check cabal file
  genConfig                generate config file
#+end_example

* App Configuration

The configuration of cabal-fix is encapsulated in the Config type. The configuration file generated by the app (via =cabal-fix genConfig=) just (pretty) prints defaultConfig.

#+begin_src haskell-ng :results output :exports both
import Text.Pretty.Simple
pPrint defaultConfig
#+end_src

#+RESULTS:
#+begin_example
Config
    { freeTexts = [ "description" ]
    , fieldRemovals = []
    , preferredDeps =
        [
            ( "base"
            , ">=4.7 && <5"
            )
        ]
    , addFields = []
    , fixCommas =
        [
            ( "extra-doc-files"
            , NoCommas
            )
        ,
            ( "build-depends"
            , PrefixCommas
            )
        ]
    , sortFieldLines =
        [ "build-depends"
        , "exposed-modules"
        , "default-extensions"
        , "ghc-options"
        , "extra-doc-files"
        , "tested-with"
        ]
    , sortFields = True
    , fieldOrdering =
        [
            ( "cabal-version"
            , 0.0
            )
        ,
            ( "import"
            , 1.0
            )
        ,
            ( "main-is"
            , 2.0
            )
        ,
            ( "default-language"
            , 3.0
            )
        ,
            ( "name"
            , 4.0
            )
        ,
            ( "hs-source-dirs"
            , 5.0
            )
        ,
            ( "version"
            , 6.0
            )
        ,
            ( "build-depends"
            , 7.0
            )
        ,
            ( "exposed-modules"
            , 8.0
            )
        ,
            ( "license"
            , 9.0
            )
        ,
            ( "license-file"
            , 10.0
            )
        ,
            ( "other-modules"
            , 11.0
            )
        ,
            ( "copyright"
            , 12.0
            )
        ,
            ( "category"
            , 13.0
            )
        ,
            ( "author"
            , 14.0
            )
        ,
            ( "default-extensions"
            , 15.0
            )
        ,
            ( "ghc-options"
            , 16.0
            )
        ,
            ( "maintainer"
            , 17.0
            )
        ,
            ( "homepage"
            , 18.0
            )
        ,
            ( "bug-reports"
            , 19.0
            )
        ,
            ( "synopsis"
            , 20.0
            )
        ,
            ( "description"
            , 21.0
            )
        ,
            ( "build-type"
            , 22.0
            )
        ,
            ( "tested-with"
            , 23.0
            )
        ,
            ( "extra-doc-files"
            , 24.0
            )
        ,
            ( "source-repository"
            , 25.0
            )
        ,
            ( "type"
            , 26.0
            )
        ,
            ( "common"
            , 27.0
            )
        ,
            ( "location"
            , 28.0
            )
        ,
            ( "library"
            , 29.0
            )
        ,
            ( "executable"
            , 30.0
            )
        ,
            ( "test-suite"
            , 31.0
            )
        ]
    , fixBuildDeps = True
    , depAlignment = DepAligned
    , removeBlankFields = True
    , valueAligned = ValueUnaligned
    , sectionMargin = Margin
    , commentMargin = NoMargin
    , narrowN = 60
    , indentN = 4
    }
#+end_example

* Library Usage

#+begin_src haskell-ng :results output :exports both
:set -XOverloadedStrings
:set -XOverloadedLabels
:set -Wno-incomplete-uni-patterns
:set -Wno-name-shadowing
import CabalFix
import Optics.Extra
import Data.ByteString.Char8 qualified as C
bs = minimalExampleBS
cfg = defaultConfig
(Just cf) = preview (cabalFields' cfg) bs
fs = cf & view (#fields % fieldList')
#+end_src

#+RESULTS:
#+begin_example
Build profile: -w ghc-9.4.8 -O1
In order, the following will be built (use -v for more details):
 - cabal-fix-0.0.0.1 (lib) (ephemeral targets)
Preprocessing library for cabal-fix-0.0.0.1..
GHCi, version 9.4.8: https://www.haskell.org/ghc/  :? for help
[1 of 4] Compiling CabalFix.FlatParse ( src/CabalFix/FlatParse.hs, interpreted )
[2 of 4] Compiling CabalFix         ( src/CabalFix.hs, interpreted )
[3 of 4] Compiling CabalFix.Archive ( src/CabalFix/Archive.hs, interpreted )
[4 of 4] Compiling CabalFix.Patch   ( src/CabalFix/Patch.hs, interpreted )
Ok, four modules loaded.
#+end_example

#+begin_src haskell-ng :results output :exports both
cf & review (cabalFields' cfg) & C.putStr
#+end_src

#+RESULTS:
#+begin_example
cabal-version: 3.0
name: minimal
version: 0.1.0.0
license: BSD-2-Clause
license-file: LICENSE
build-type: Simple
extra-doc-files: CHANGELOG.md

common warnings
    ghc-options: -Wall

library
    import: warnings
    exposed-modules: MyLib
    build-depends: base ^>=4.17.2.1
    hs-source-dirs: src
    default-language: GHC2021

test-suite minimal-test
    import: warnings
    default-language: GHC2021
    type: exitcode-stdio-1.0
    hs-source-dirs: test
    main-is: Main.hs
    build-depends:
        base ^>=4.17.2.1,
        minimal
#+end_example

* cabal init

** minimal

A minimal =cabal init=

#+begin_src sh :results output :exports both
mkdir minimal && cd minimal && cabal init --minimal --simple --overwrite --lib --tests --language=GHC2021 --license=BSD-2-Clause  -p minimal
#+end_src

#+RESULTS:
#+begin_example
[Log] Using cabal specification: 3.0
[Log] Creating fresh file LICENSE...
[Log] Creating fresh file CHANGELOG.md...
[Log] Creating fresh directory ./src...
[Log] Creating fresh file src/MyLib.hs...
[Log] Creating fresh directory ./test...
[Log] Creating fresh file test/Main.hs...
[Log] Creating fresh file minimal.cabal...
[Warning] No synopsis given. You should edit the .cabal file and add one.
[Info] You may want to edit the .cabal file and add a Description field.

#+end_example

Compared with the original =cabal init= contents, =cabal-fix=:

- widens the =base= range, in line with standard practice.
- reorders the =test-suite= section fields, in line with the ordering of the =library= section ones.

#+begin_src sh :results output :exports both
cabal-fix check -d "minimal" -c "other/minimal.config"
#+end_src

#+RESULTS:
#+begin_example
Right (Just [
  -"    build-depends:    base ^>=4.17.2.1",
  +"    build-depends:    base >=4.17 && <5",
  -"    default-language: GHC2021",
  +"    main-is:          Main.hs",
  -"    type:             exitcode-stdio-1.0",
  +"    build-depends:",
  -"    hs-source-dirs:   test",
  +"        base    >=4.17 && <5,",
  -"    main-is:          Main.hs",
  +"        minimal",
  -"    build-depends:",
  +"    hs-source-dirs:   test",
  -"        base ^>=4.17.2.1,",
  +"    default-language: GHC2021",
  -"        minimal",
  +"    type:             exitcode-stdio-1.0"])
#+end_example

** code example

For reference, the code below should produce the same results as the app run above:

#+begin_src haskell-ng :results output :exports both
:set -XOverloadedStrings
:set -XOverloadedLabels
:set -Wno-incomplete-uni-patterns
:set -Wno-name-shadowing
:set -Wno-type-defaults
import CabalFix
import Text.Pretty.Simple
import CabalFix.Patch
import Data.TreeDiff
bs = minimalExampleBS
cfg = minimalConfig
(Just cf) = preview (cabalFields' cfg) bs
bs' = review (cabalFields' cfg) cf
(Just cf') = preview (cabalFields' cfg) bs'
cfFixed = fixCabalFields cfg cf
bsFixed = review (cabalFields' cfg) cfFixed
fmap ansiWlBgEditExpr $ patch (C.lines bs) (C.lines bsFixed)
#+end_src

#+RESULTS:
#+begin_example
Just [
  -"    build-depends:    base ^>=4.17.2.1",
  +"    build-depends:    base >=4.17 && <5",
  -"    default-language: GHC2021",
  +"    main-is:          Main.hs",
  -"    type:             exitcode-stdio-1.0",
  +"    build-depends:",
  -"    hs-source-dirs:   test",
  +"        base    >=4.17 && <5,",
  -"    main-is:          Main.hs",
  +"        minimal",
  -"    build-depends:",
  +"    hs-source-dirs:   test",
  -"        base ^>=4.17.2.1,",
  +"    default-language: GHC2021",
  -"        minimal",
  +"    type:             exitcode-stdio-1.0"]
#+end_example

** simple

#+begin_src sh :results output :exports both
mkdir simple && cd simple && cabal init --simple --overwrite --lib --tests --language=GHC2021 --license=BSD-2-Clause  -p simple
#+end_src

#+RESULTS:
#+begin_example
[Log] Using cabal specification: 3.0
[Log] Creating fresh file LICENSE...
[Log] Creating fresh file CHANGELOG.md...
[Log] Creating fresh directory ./src...
[Log] Creating fresh file src/MyLib.hs...
[Log] Creating fresh directory ./test...
[Log] Creating fresh file test/Main.hs...
[Log] Creating fresh file simple.cabal...
[Warning] No synopsis given. You should edit the .cabal file and add one.
[Info] You may want to edit the .cabal file and add a Description field.

#+end_example

#+begin_src sh :results output :exports both
cabal-fix check -d "simple" -c "other/minimal.config"
#+end_src

#+RESULTS:
#+begin_example
Right (Just [
  +"cabal-version:   3.0",
  -"cabal-version:      3.0",
  +"",
  -"name:               simple",
  +"name:            simple",
  -"version:            0.1.0.0",
  +"version:         0.1.0.0",
  -"license:            BSD-2-Clause",
  +"license:         BSD-2-Clause",
  -"license-file:       LICENSE",
  +"license-file:    LICENSE",
  -"build-type:         Simple",
  +"build-type:      Simple",
  -"extra-doc-files:    CHANGELOG.md",
  +"extra-doc-files: CHANGELOG.md",
  -"    build-depends:    base ^>=4.17.2.1",
  +"    build-depends:    base >=4.17 && <5",
  -"    -- Base language which the package is written in.",
  +"    -- The entrypoint to the test suite.",
  -"    default-language: GHC2021",
  +"    main-is:          Main.hs",
  -"    -- Modules included in this executable, other than Main.",
  -"    -- other-modules:",
  +"    -- Test dependencies.",
  -"",
  +"    build-depends:",
  -"    -- LANGUAGE extensions used by modules in this package.",
  +"        base   >=4.17 && <5,",
  -"    -- other-extensions:",
  +"        simple",
  -"    -- The interface type and version of the test suite.",
  +"    -- Directories containing source files.",
  -"    type:             exitcode-stdio-1.0",
  +"    hs-source-dirs:   test",
  -"    -- Directories containing source files.",
  +"    -- Base language which the package is written in.",
  -"    hs-source-dirs:   test",
  +"    default-language: GHC2021",
  -"    -- The entrypoint to the test suite.",
  +"    -- Modules included in this executable, other than Main.",
  -"    main-is:          Main.hs",
  +"    -- other-modules:",
  +"    -- LANGUAGE extensions used by modules in this package.",
  -"    -- Test dependencies.",
  +"    -- other-extensions:",
  -"    build-depends:",
  +"",
  -"        base ^>=4.17.2.1,",
  +"    -- The interface type and version of the test suite.",
  -"        simple",
  +"    type:             exitcode-stdio-1.0"])
#+end_example

* Archive Exploration

CabalFix.Archive contains functions to extract and explore cabal files listed in your cabal index file.

The sections below are some exploration notes.

** imports
#+begin_src haskell-ng :results output :exports both
:r
:set -Wno-type-defaults
:set -Wno-name-shadowing
:set -XOverloadedLabels
:set -XOverloadedStrings
:set -Wno-incomplete-uni-patterns
import Algebra.Graph
import Algebra.Graph.ToGraph qualified as ToGraph
import CabalFix
import CabalFix.Archive
import CabalFix.FlatParse
import Codec.Archive.Tar qualified as Tar
import Control.Monad
import Data.Bifunctor
import Data.ByteString (ByteString)
import Data.ByteString qualified as BS
import Data.ByteString.Char8 qualified as C
import Data.ByteString.Lazy qualified as BSL
import Data.Char
import Data.Either
import Data.Function
import Data.List qualified as List
import Data.Map.Strict qualified as Map
import Data.Ord
import Data.Set qualified as Set
import DotParse
import FlatParse.Basic qualified as FP
import System.Directory
import Text.Pretty.Simple
#+end_src

#+RESULTS:
: Ok, four modules loaded.

** tar file to list of cabal files
*** entries

#+begin_src haskell-ng :results output :exports both
es <- cabalEntries
length es
#+end_src

#+RESULTS:
: 317368

#+begin_src haskell-ng :results output :exports both
Tar.entryPath <$> take 5 es
#+end_src

#+RESULTS:
: ["iconv/0.2/iconv.cabal","Crypto/3.0.3/Crypto.cabal","HDBC/1.0.1/HDBC.cabal","HDBC-odbc/1.0.1.0/HDBC-odbc.cabal","HDBC-postgresql/1.0.1.0/HDBC-postgresql.cabal"]

They are all normal files

#+begin_src haskell-ng :results output :exports both
(length [x | (Tar.NormalFile x _) <- Tar.entryContent <$> es])
#+end_src

#+RESULTS:
: 317368

*** Maximum file size:

#+begin_src haskell-ng :results output :exports both
(\xs -> filter ((maximum (snd <$> xs) ==) . snd) xs) $ [(fp,x) | (fp, Tar.NormalFile _ x) <- (\e -> (Tar.entryPath e, Tar.entryContent e)) <$> es]
#+end_src

#+RESULTS:
: [("acme-everything/2018.11.18/acme-everything.cabal",261865)]

*** zero size

#+begin_src haskell-ng :results output :exports both
take 4 $ (\xs -> filter ((0 ==) . snd) xs) $ [(fp,x) | (fp, Tar.NormalFile _ x) <- (\e -> (Tar.entryPath e, Tar.entryContent e)) <$> es]
#+end_src

#+RESULTS:
: [("lzma/preferred-versions",0),("signal/preferred-versions",0),("peyotls-codec/preferred-versions",0),("th-orphans/preferred-versions",0)]

*** preferred-versions

[[https://hackage.haskell.org/package/Cabal/preferred][Cabal: preferred and deprecated versions | Hackage]]

#+begin_src haskell-ng :results output :exports both
take 3 $ (\xs -> filter ((List.isSuffixOf "preferred-versions") . fst) xs) $ [(fp,bs) | (fp, Tar.NormalFile bs _) <- (\e -> (Tar.entryPath e, Tar.entryContent e)) <$> es]
#+end_src

#+RESULTS:
: [("ADPfusion/preferred-versions","ADPfusion <0.4.0.0 || >0.4.0.0"),("AesonBson/preferred-versions","AesonBson <0.2.0 || >0.2.0 && <0.2.1 || >0.2.1"),("BiobaseXNA/preferred-versions","BiobaseXNA <0.9.1.0 || >0.9.1.0")]

#+begin_src haskell-ng :results output :exports both
length $ (\xs -> filter ((List.isSuffixOf "preferred-versions") . fst) xs) $ [(fp,bs) | (fp, Tar.NormalFile bs _) <- (\e -> (Tar.entryPath e, Tar.entryContent e)) <$> es]
#+end_src

#+RESULTS:
: 3376

*** package.json

=package-json= content is a security/signing feature you can read about in [[https://github.com/haskell-ng/hackage-security/blob/master/README.md][hackage-security]].

#+begin_src haskell-ng :results output :exports both
length $ filter ((== "package.json") . filenameFN . runParser_ filenameP . FP.strToUtf8 . fst) $ filter (not . (List.isSuffixOf "preferred-versions") . fst) $ [(fp,bs) | (fp, Tar.NormalFile bs _) <- (\e -> (Tar.entryPath e, Tar.entryContent e)) <$> es]
#+end_src

#+RESULTS:
: 137524

*** cabal files

Unique package/version combinations.

There are multiple versions of package/versions because of revisions. See [[https://github.com/haskell-infra/hackage-trustees/blob/master/revisions-information.md][revisions-information.md]]

Unique =*/*.cabal/version= entries

#+begin_src haskell-ng :results output :exports both
cs <- cabals
length cs
#+end_src

#+RESULTS:
: 137524

Unique cabal packages

#+begin_src haskell-ng :results output :exports both
lcs <- latestCabals
Map.size lcs
#+end_src

#+RESULTS:
: 17631

Average number of versions per package

#+begin_src haskell-ng :results output :exports both
(fromIntegral (length cs)) / fromIntegral (Map.size lcs)
#+end_src

#+RESULTS:
: 7.800124780216664

** latestCabals to CabalFields map

#+begin_src haskell-ng :results output :exports both
lcs <- latestCabals defaultConfig
Map.size lcs
cfg = defaultConfig
lcs' = fmap (second (parseCabalFields cfg)) lcs
Map.size $ Map.filter (snd >>> isLeft) lcs'
:t lcs'
badParse = Map.filter (isLeft . parseCabalFields cfg . snd) lcs
Map.size badParse
#+end_src

#+RESULTS:
: 17631
: 6
: lcs' :: Map.Map ByteString (Version, Either ByteString CabalFields)
: 6
** CabalFields map to dependency graph

#+begin_src haskell-ng :results output :exports both
lcfs <- latestCabalFields
vlds = validLibDeps $ fmap snd lcfs
Map.size vlds
depG = allDepGraph $ fmap snd lcfs
vertexCount depG
edgeCount depG
#+end_src

#+RESULTS:
: 15547
: 15621
: 107566

** algebraic-graphs

An (algebraic) graph of dependencies:

=text= package dependency example

#+begin_src haskell-ng
supers = upstreams "text" depG <> Set.singleton "text"
superG = induce (`elem` (Data.Foldable.toList supers)) depG
#+end_src

#+RESULTS:

#+begin_src haskell-ng :results output :exports both
supers
#+end_src

#+RESULTS:
: fromList ["array","binary","bytestring","deepseq","ghc-prim","template-haskell","text"]


#+begin_src haskell-ng :file other/textdeps.svg :results output graphics file :exports results
 baseGraph = defaultGraph & attL GraphType (ID "size") .~ Just (IDQuoted "5!") & attL NodeType (ID "shape") .~ Just (ID "box") & attL NodeType (ID "height") .~ Just (ID 2) & gattL (ID "rankdir") .~ Just (IDQuoted "TB")
 g = toDotGraphWith Directed baseGraph superG
 processDotWith Directed ["-Tsvg", "-oother/textdeps.svg"] (dotPrint defaultDotConfig g)
 BS.writeFile "other/textdeps.dot" (dotPrint defaultDotConfig g)
 #+end_src

#+RESULTS:
[[file:other/textdeps.svg]]

** sections
*** section count

#+begin_src haskell-ng :results output :exports both
cfs = lcfs & Map.toList & fmap (snd . snd)
cfs & toListOf (each % #fields % fieldList') & fmap (filter isSection >>> length) & count_
#+end_src

#+RESULTS:
: fromList [(0,359),(1,2559),(2,5508),(3,4730),(4,2224),(5,956),(6,479),(7,236),(8,138),(9,98),(10,63),(11,57),(12,31),(13,32),(14,22),(15,16),(16,12),(17,7),(18,11),(19,8),(20,8),(21,8),(22,4),(23,3),(24,7),(25,4),(26,6),(27,1),(28,1),(29,4),(30,2),(32,4),(33,2),(34,4),(36,1),(37,4),(38,1),(39,2),(40,1),(41,1),(43,2),(47,2),(48,2),(50,1),(65,1),(93,1),(97,1),(295,1)]

*** section types

#+begin_src haskell-ng
cfs & toListOf (each % #fields % fieldList') & fmap (filter isSection) & fmap (fmap (view fieldName')) & mconcat & count_ & Map.toList & List.sortOn (Down . snd)

#+end_src

#+RESULTS:
: [("library",16028),("source-repository",13889),("test-suite",8718),("executable",7292),("flag",4134),("common",2302),("benchmark",1246),("custom-setup",321),("foreign-library",4)]

combinations:

#+begin_src haskell-ng :results output :exports both
cfs & toListOf (each % #fields % fieldList') & fmap (filter isSection) & fmap (fmap (view fieldName')) & fmap (filter (not . (flip List.elem) ["source-repository", "custom-setup", "foreign-library", "flag", "common"])) & fmap (count_ >>> Map.toList >>> List.sortOn fst) & count_ & Map.toList & List.sortOn (Down . snd) & take 10
#+end_src

#+RESULTS:
: [([("library",1)],7291),([("library",1),("test-suite",1)],4195),([("executable",1),("library",1)],1148),([("executable",1)],1105),([("executable",1),("library",1),("test-suite",1)],901),([("benchmark",1),("library",1),("test-suite",1)],520),([("library",1),("test-suite",2)],416),([],359),([("executable",2),("library",1)],163),([("executable",2),("library",1),("test-suite",1)],133)]

at least 1 combinations:

#+begin_src haskell-ng :results output :exports both
cfs & toListOf (each % #fields % fieldList') & fmap (filter isSection) & fmap (fmap (view fieldName')) & fmap (filter (not . (flip List.elem) ["source-repository", "custom-setup", "foreign-library", "flag", "common"])) & fmap (count_ >>> Map.toList >>> fmap fst >>> List.sortOn id) & count_ & Map.toList & List.sortOn (Down . snd) & take 10
#+end_src

#+RESULTS:
: [(["library"],7297),(["library","test-suite"],4778),(["executable","library"],1490),(["executable","library","test-suite"],1309),(["executable"],1263),(["benchmark","library","test-suite"],739),([],359),(["benchmark","executable","library","test-suite"],182),(["executable","test-suite"],119),(["benchmark","library"],59)]

*** section in section

#+begin_src haskell-ng :results output :exports both
sections' = to (filter isSection)
-- cfs & fmap (foldOf (#fields % fieldList' % sections' % each % secFields' % sections')) & filter (not . null) & fmap (second (fmap (view fieldName'))) & fmap snd & mconcat & count_
cfs & fmap (foldOf (#fields % fieldList' % sections' % each % secFields' % sections')) & filter (not . null) & fmap ((fmap (view fieldName'))) & mconcat & count_
#+end_src

#+RESULTS:
: fromList [("elif",52),("else",3203),("if",11459),("library",3)]

Embedded libraries are all deprecated.

*** zero-section cfs

Looks like library fields used to be allowed at the top level...

#+begin_src haskell-ng :results output :exports both
cfs0 = cfs & toListOf (each % #fields % fieldList') & filter ((==0) . length . (filter isSection))
length cfs0
count_ $ cfs0 & fmap (foldOf (field' "build-depends") >>> length)
cfs00 = cfs0 & filter (foldOf (field' "build-depends") >>> length >>> (==0))
length cfs00
#+end_src

#+RESULTS:
: 359
: fromList [(0,2),(1,349),(2,7),(4,1)]
: 2

** Dependency counts

package dependency count:

#+begin_src haskell-ng :results output :exports both
lcfs & fmap (snd >>> libDeps >>> fmap dep >>> List.nub >>> length) & Map.toList & List.sortOn (Down . snd) & take 20
#+end_src

#+RESULTS:
: [("acme-everything",7533),("yesod-platform",132),("planet-mitchell",109),("freckle-app",78),("cachix",76),("btc-lsp",71),("too-many-cells",70),("swarm",68),("ghcide",67),("pandoc",67),("sprinkles",65),("pantry-tmp",64),("taffybar",63),("NGLess",60),("project-m36",59),("stack",59),("espial",58),("hermes",58),("purescript",56),("futhark",55)]

dependency count:

#+begin_src haskell-ng :results output :exports both
lcfs & fmap (snd >>> libDeps >>> fmap dep >>> List.nub) & Map.toList & fmap snd & mconcat & count_ & Map.toList & List.sortOn (snd >>> Down) & take 40
#+end_src

#+RESULTS:
: [("base",14883),("bytestring",5384),("text",4972),("containers",4753),("mtl",3468),("transformers",3070),("aeson",2013),("time",1961),("vector",1793),("directory",1597),("filepath",1510),("template-haskell",1472),("unordered-containers",1392),("deepseq",1240),("lens",1173),("hashable",930),("binary",929),("array",892),("exceptions",855),("process",844),("stm",819),("random",811),("http-types",784),("attoparsec",781),("network",756),("parsec",744),("data-default",609),("QuickCheck",597),("conduit",503),("http-client",497),("split",472),("primitive",470),("ghc-prim",456),("async",449),("semigroups",427),("monad-control",424),("scientific",420),("resourcet",401),("unix",398),("utf8-string",392)]

** version ranges

#+begin_src haskell-ng :results output :exports both
cs <- cabals
length cs
#+end_src

#+RESULTS:
: 137323

#+begin_src haskell-ng :results output :exports both
:t cs

mVersions = Map.fromListWith (<>) $ ((\x -> (nameFN x, (:[]) $ (versionInts $ versionFN x))) . fst) <$> cs
Map.size mVersions
#+end_src

#+RESULTS:
: cs :: [(FileName, ByteString)]
: 17631

#+begin_src haskell-ng :results output :exports both
(Just x1) = Map.lookup "chart-svg" mVersions
x1
minimum x1
maximum x1
#+end_src

#+RESULTS:
: [[0,6,0,0],[0,5,2,0],[0,5,1,1],[0,5,1,0],[0,5,0,0],[0,4,1,1],[0,4,1,0],[0,4,0],[0,3,3],[0,3,2],[0,3,1],[0,3,0],[0,2,3],[0,2,2],[0,2,1],[0,2,0],[0,1,3],[0,1,2],[0,1,1],[0,1,0],[0,0,3],[0,0,2],[0,0,1]]
: [0,0,1]
: [0,6,0,0]

*** all versions are unique?

#+begin_src haskell-ng :results output :exports both
take 10 $ Map.toList $ Map.filter (\a -> length a /= length (List.nub a)) mVersions
#+end_src

#+RESULTS:
: []

*** Version counts

#+begin_src haskell-ng :results output :exports both
take 10 $ List.sortOn (Down . snd) $ Map.toList $ Map.map length mVersions
#+end_src

#+RESULTS:
: [("haskoin-store",298),("git-annex",282),("hlint",221),("yesod-core",216),("purescript",204),("warp",204),("pandoc",193),("hakyll",192),("egison",190),("persistent",186)]

* Field re-ordering

#+begin_src haskell-ng :results output :exports both
zipWith (\o l -> (fst l, o)) [0..] (List.sortOn snd $ fieldOrdering defaultConfig)
#+end_src

#+RESULTS:
: [("cabal-version",0),("import",1),("main-is",2),("default-language",3),("name",4),("hs-source-dirs",5),("version",6),("build-depends",7),("exposed-modules",8),("license",9),("license-file",10),("other-modules",11),("copyright",12),("category",13),("author",14),("default-extensions",15),("ghc-options",16),("maintainer",17),("homepage",18),("bug-reports",19),("synopsis",20),("description",21),("build-type",22),("tested-with",23),("extra-doc-files",24),("source-repository",25),("type",26),("common",27),("location",28),("library",29),("executable",30),("test-suite",31)]

* references

[[https://hackage.haskell.org/package/Cabal-syntax-3.10.2.0/docs/Distribution-Fields-Field.html][Distribution.Fields.Field]]

[[https://hackage.haskell.org/package/optics-core-0.4.1.1][optics-core: Optics as an abstract interface: core definitions]]

[[https://cabal.readthedocs.io/en/stable/cabal-package.html#package-descriptions][6. Package Description — Cabal 3.10.1.0 User's Guide]]