cabal-fix 0.0.0.2 → 0.1.0.0
raw patch · 7 files changed
+873/−919 lines, 7 filesdep +doctest-paralleldep ~Cabal-syntaxPVP ok
version bump matches the API change (PVP)
Dependencies added: doctest-parallel
Dependency ranges changed: Cabal-syntax
API changes (from Hackage documentation)
Files
- ChangeLog.md +5/−0
- cabal-fix.cabal +20/−65
- readme.md +828/−0
- readme.org +0/−838
- src/CabalFix.hs +12/−13
- src/CabalFix/Archive.hs +2/−3
- test/doctests.hs +6/−0
ChangeLog.md view
@@ -1,3 +1,8 @@++# 0.1++* Added doctests+ # 0.0.0.1 * conception
cabal-fix.cabal view
@@ -1,31 +1,29 @@ cabal-version: 3.0 name: cabal-fix-version: 0.0.0.2+version: 0.1.0.0 license: BSD-3-Clause license-file: LICENSE category: distribution author: Tony Day maintainer: tonyday567@gmail.com-homepage: https://github.com/tonyday567/cabal-fixes#readme-bug-reports: https://github.com/tonyday567/cabal-fixes/issues+homepage: https://github.com/tonyday567/cabal-fix#readme+bug-reports: https://github.com/tonyday567/cabal-fix/issues synopsis: Fix for cabal files. description: An executable and library to help fix cabal files and explore cabal. build-type: Simple tested-with:- GHC == 8.10.7- GHC == 9.2.8- GHC == 9.4.8- GHC == 9.6.4- GHC == 9.8.1+ , GHC == 9.10.1+ , GHC == 9.6.5+ , GHC == 9.8.2 extra-doc-files: ChangeLog.md other/textdeps.svg- readme.org+ readme.md source-repository head type: git- location: https://github.com/tonyday567/cabal-fixes+ location: https://github.com/tonyday567/cabal-fix common ghc-options-exe-stanza ghc-options:@@ -44,67 +42,14 @@ -Wredundant-constraints common ghc2021-stanza- if impl ( ghc >= 9.2 )- default-language: GHC2021-- if impl ( ghc < 9.2 )- default-language: Haskell2010- default-extensions:- BangPatterns- BinaryLiterals- ConstrainedClassMethods- ConstraintKinds- DeriveDataTypeable- DeriveFoldable- DeriveFunctor- DeriveGeneric- DeriveLift- DeriveTraversable- DoAndIfThenElse- EmptyCase- EmptyDataDecls- EmptyDataDeriving- ExistentialQuantification- ExplicitForAll- FlexibleContexts- FlexibleInstances- ForeignFunctionInterface- GADTSyntax- GeneralisedNewtypeDeriving- HexFloatLiterals- ImplicitPrelude- InstanceSigs- KindSignatures- MonomorphismRestriction- MultiParamTypeClasses- NamedFieldPuns- NamedWildCards- NumericUnderscores- PatternGuards- PolyKinds- PostfixOperators- RankNTypes- RelaxedPolyRec- ScopedTypeVariables- StandaloneDeriving- StarIsType- TraditionalRecordSyntax- TupleSections- TypeApplications- TypeOperators- TypeSynonymInstances-- if impl ( ghc < 9.2 ) && impl ( ghc >= 8.10 )- default-extensions:- ImportQualifiedPost- StandaloneKindSignatures+ default-language: GHC2021 library import: ghc-options-stanza import: ghc2021-stanza hs-source-dirs: src build-depends:- , Cabal-syntax >=3.10 && <3.11+ , Cabal-syntax >=3.10 && <3.15 , algebraic-graphs >=0.7 && <0.8 , base >=4.14 && <5 , bytestring >=0.11 && <0.13@@ -142,3 +87,13 @@ , pretty-simple >=4.1 && <4.2 , text >=2.0 && <2.2 , tree-diff >=0.3 && <0.4++test-suite doctests+ import: ghc2021-stanza+ main-is: doctests.hs+ hs-source-dirs: test+ build-depends:+ , base >=4.14 && <5+ , doctest-parallel >=0.3 && <0.4+ ghc-options: -threaded+ type: exitcode-stdio-1.0
+ readme.md view
@@ -0,0 +1,828 @@++# Table of Contents++1. [cabal-fix](#orgbfbcd8c)+2. [App Usage](#org2f4aafb)+3. [App Configuration](#orga921360)+4. [Library Usage](#orgbb0e777)+5. [cabal init](#orge6fca8d)+ 1. [minimal](#orgc14dafe)+ 2. [code example](#orgd8c294c)+ 3. [simple](#org2493565)+6. [Archive Exploration](#org733a366)+ 1. [imports](#org69e0aa6)+ 2. [tar file to list of cabal files](#org241dfdb)+ 1. [entries](#orgdc43fe9)+ 2. [Maximum file size:](#org39c1923)+ 3. [zero size](#orgcf21c34)+ 4. [preferred-versions](#orgb39b7ed)+ 5. [package.json](#org9b4f148)+ 6. [cabal files](#orge4acb82)+ 3. [latestCabals to CabalFields map](#org9ce30f8)+ 4. [CabalFields map to dependency graph](#orgb4f4cbb)+ 5. [algebraic-graphs](#org6f1757a)+ 6. [sections](#orgd2876cc)+ 1. [section count](#org39f7aed)+ 2. [section types](#orge952475)+ 3. [section in section](#org74c0b33)+ 4. [zero-section cfs](#orgd5e491e)+ 7. [Dependency counts](#org9a284e6)+ 8. [version ranges](#org198f27d)+ 1. [all versions are unique?](#org0b08b16)+ 2. [Version counts](#org39af952)+7. [Field re-ordering](#org9771b7b)+8. [references](#org15314af)+++<a id="orgbfbcd8c"></a>++# cabal-fix++[](https://hackage.haskell.org/package/cabal-fix)+[](https://github.com/tonyday567/cabal-fix/actions?query=workflow%3Ahaskell-ci)++`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.+++<a id="org2f4aafb"></a>++# App Usage++ + #+end_export+ cabal-fix --help++ 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+++<a id="orga921360"></a>++# 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.++ import Text.Pretty.Simple+ pPrint defaultConfig++ 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+ }+++<a id="orgbb0e777"></a>++# Library Usage++ :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')++ 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.++ cf & review (cabalFields' cfg) & C.putStr++ 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+++<a id="orge6fca8d"></a>++# cabal init+++<a id="orgc14dafe"></a>++## minimal++A minimal `cabal init`++ mkdir minimal && cd minimal && cabal init --minimal --simple --overwrite --lib --tests --language=GHC2021 --license=BSD-2-Clause -p minimal++ [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.++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.++ cabal-fix check -d "minimal" -c "other/minimal.config"++ Right (Just [+ -" build-depends: base ^>=4.17.2.1",+ +" build-depends: base >=4.14 && <5",+ -" default-language: GHC2021",+ +" main-is: Main.hs",+ -" type: exitcode-stdio-1.0",+ +" build-depends:",+ -" hs-source-dirs: test",+ +" base >=4.14 && <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"])+++<a id="orgd8c294c"></a>++## code example++For reference, the code below should produce the same results as the app run above:++ :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)++ Just [+ -" build-depends: base ^>=4.17.2.1",+ +" build-depends: base >=4.14 && <5",+ -" default-language: GHC2021",+ +" main-is: Main.hs",+ -" type: exitcode-stdio-1.0",+ +" build-depends:",+ -" hs-source-dirs: test",+ +" base >=4.14 && <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"]+++<a id="org2493565"></a>++## simple++ mkdir simple && cd simple && cabal init --simple --overwrite --lib --tests --language=GHC2021 --license=BSD-2-Clause -p simple++ [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.++ cabal-fix check -d "simple" -c "other/minimal.config"++ 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.14 && <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.14 && <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"])+++<a id="org733a366"></a>++# 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.+++<a id="org69e0aa6"></a>++## imports++ :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++ Ok, four modules loaded.+++<a id="org241dfdb"></a>++## tar file to list of cabal files+++<a id="orgdc43fe9"></a>++### entries++ es <- cabalEntries+ length es++ 317368++ Tar.entryPath <$> take 5 es++ ["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++ (length [x | (Tar.NormalFile x _) <- Tar.entryContent <$> es])++ 317368+++<a id="org39c1923"></a>++### Maximum file size:++ (\xs -> filter ((maximum (snd <$> xs) ==) . snd) xs) $ [(fp,x) | (fp, Tar.NormalFile _ x) <- (\e -> (Tar.entryPath e, Tar.entryContent e)) <$> es]++ [("acme-everything/2018.11.18/acme-everything.cabal",261865)]+++<a id="orgcf21c34"></a>++### zero size++ take 4 $ (\xs -> filter ((0 ==) . snd) xs) $ [(fp,x) | (fp, Tar.NormalFile _ x) <- (\e -> (Tar.entryPath e, Tar.entryContent e)) <$> es]++ [("lzma/preferred-versions",0),("signal/preferred-versions",0),("peyotls-codec/preferred-versions",0),("th-orphans/preferred-versions",0)]+++<a id="orgb39b7ed"></a>++### preferred-versions++[Cabal: preferred and deprecated versions | Hackage](https://hackage.haskell.org/package/Cabal/preferred)++ take 3 $ (\xs -> filter ((List.isSuffixOf "preferred-versions") . fst) xs) $ [(fp,bs) | (fp, Tar.NormalFile bs _) <- (\e -> (Tar.entryPath e, Tar.entryContent e)) <$> es]++ [("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")]++ length $ (\xs -> filter ((List.isSuffixOf "preferred-versions") . fst) xs) $ [(fp,bs) | (fp, Tar.NormalFile bs _) <- (\e -> (Tar.entryPath e, Tar.entryContent e)) <$> es]++ 3376+++<a id="org9b4f148"></a>++### package.json++`package-json` content is a security/signing feature you can read about in [hackage-security](https://github.com/haskell-ng/hackage-security/blob/master/README.md).++ 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]++ 137524+++<a id="orge4acb82"></a>++### cabal files++Unique package/version combinations.++There are multiple versions of package/versions because of revisions. See [revisions-information.md](https://github.com/haskell-infra/hackage-trustees/blob/master/revisions-information.md)++Unique `*/*.cabal/version` entries++ cs <- cabals+ length cs++ 137524++Unique cabal packages++ lcs <- latestCabals+ Map.size lcs++ 17631++Average number of versions per package++ (fromIntegral (length cs)) / fromIntegral (Map.size lcs)++ 7.800124780216664+++<a id="org9ce30f8"></a>++## latestCabals to CabalFields map++ 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++ 17631+ 6+ lcs' :: Map.Map ByteString (Version, Either ByteString CabalFields)+ 6+++<a id="orgb4f4cbb"></a>++## CabalFields map to dependency graph++ lcfs <- latestCabalFields+ vlds = validLibDeps $ fmap snd lcfs+ Map.size vlds+ depG = allDepGraph $ fmap snd lcfs+ vertexCount depG+ edgeCount depG++ 15547+ 15621+ 107566+++<a id="org6f1757a"></a>++## algebraic-graphs++An (algebraic) graph of dependencies:++`text` package dependency example++ supers = upstreams "text" depG <> Set.singleton "text"+ superG = induce (`elem` (Data.Foldable.toList supers)) depG++ supers++ fromList ["array","binary","bytestring","deepseq","ghc-prim","template-haskell","text"]++ 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)+++++<a id="orgd2876cc"></a>++## sections+++<a id="org39f7aed"></a>++### section count++ cfs = lcfs & Map.toList & fmap (snd . snd)+ cfs & toListOf (each % #fields % fieldList') & fmap (filter isSection >>> length) & count_++ 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)]+++<a id="orge952475"></a>++### section types++ cfs & toListOf (each % #fields % fieldList') & fmap (filter isSection) & fmap (fmap (view fieldName')) & mconcat & count_ & Map.toList & List.sortOn (Down . snd)++ [("library",16028),("source-repository",13889),("test-suite",8718),("executable",7292),("flag",4134),("common",2302),("benchmark",1246),("custom-setup",321),("foreign-library",4)]++combinations:++ 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++ [([("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:++ 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++ [(["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)]+++<a id="org74c0b33"></a>++### section in section++ 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_++ fromList [("elif",52),("else",3203),("if",11459),("library",3)]++Embedded libraries are all deprecated.+++<a id="orgd5e491e"></a>++### zero-section cfs++Looks like library fields used to be allowed at the top level…++ 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++ 359+ fromList [(0,2),(1,349),(2,7),(4,1)]+ 2+++<a id="org9a284e6"></a>++## Dependency counts++package dependency count:++ lcfs & fmap (snd >>> libDeps >>> fmap dep >>> List.nub >>> length) & Map.toList & List.sortOn (Down . snd) & take 20++ [("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:++ lcfs & fmap (snd >>> libDeps >>> fmap dep >>> List.nub) & Map.toList & fmap snd & mconcat & count_ & Map.toList & List.sortOn (snd >>> Down) & take 40++ [("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)]+++<a id="org198f27d"></a>++## version ranges++ cs <- cabals+ length cs++ 137323++ :t cs+ + mVersions = Map.fromListWith (<>) $ ((\x -> (nameFN x, (:[]) $ (versionInts $ versionFN x))) . fst) <$> cs+ Map.size mVersions++ cs :: [(FileName, ByteString)]+ 17631++ (Just x1) = Map.lookup "chart-svg" mVersions+ x1+ minimum x1+ maximum x1++ [[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]+++<a id="org0b08b16"></a>++### all versions are unique?++ take 10 $ Map.toList $ Map.filter (\a -> length a /= length (List.nub a)) mVersions++ []+++<a id="org39af952"></a>++### Version counts++ take 10 $ List.sortOn (Down . snd) $ Map.toList $ Map.map length mVersions++ [("haskoin-store",298),("git-annex",282),("hlint",221),("yesod-core",216),("purescript",204),("warp",204),("pandoc",193),("hakyll",192),("egison",190),("persistent",186)]+++<a id="org9771b7b"></a>++# Field re-ordering++ zipWith (\o l -> (fst l, o)) [0..] (List.sortOn snd $ fieldOrdering defaultConfig)++ [("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)]+++<a id="org15314af"></a>++# references++[Distribution.Fields.Field](https://hackage.haskell.org/package/Cabal-syntax-3.10.2.0/docs/Distribution-Fields-Field.html)++[optics-core: Optics as an abstract interface: core definitions](https://hackage.haskell.org/package/optics-core-0.4.1.1)++[6. Package Description — Cabal 3.10.1.0 User’s Guide](https://cabal.readthedocs.io/en/stable/cabal-package.html#package-descriptions)+
− readme.org
@@ -1,838 +0,0 @@-* cabal-fix--[[https://hackage.haskell.org/package/cabal-fix][https://img.shields.io/hackage/v/cabal-fix.svg]]-[[https://github.com/tonyday567/cabal-fix/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.14 && <5",- -" default-language: GHC2021",- +" main-is: Main.hs",- -" type: exitcode-stdio-1.0",- +" build-depends:",- -" hs-source-dirs: test",- +" base >=4.14 && <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.14 && <5",- -" default-language: GHC2021",- +" main-is: Main.hs",- -" type: exitcode-stdio-1.0",- +" build-depends:",- -" hs-source-dirs: test",- +" base >=4.14 && <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.14 && <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.14 && <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]]
src/CabalFix.hs view
@@ -1,15 +1,13 @@ {-# LANGUAGE DataKinds #-}-{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}-{-# OPTIONS_GHC -Wno-name-shadowing #-} {-# OPTIONS_GHC -Wno-orphans #-}-{-# OPTIONS_GHC -Wno-unused-matches #-}+{-# OPTIONS_GHC -Wno-x-partial #-} --- | Tools to print, parse and fix cabal files, 'ByteString' and 'Field' lists.+-- | Tools to print, parse and fix cabal files. module CabalFix ( -- * Usage -- $usage@@ -104,6 +102,7 @@ -- -- >>> :set -XOverloadedStrings -- >>> :set -XOverloadedLabels+-- >>> :set -Wno-x-partial -- >>> import CabalFix -- >>> import Optics.Extra -- >>> import Data.ByteString.Char8 qualified as C@@ -374,10 +373,10 @@ fieldSet :: FieldName -> CabalFields -> Maybe (Field Comment) -> CabalFields fieldSet name cf f = case V.findIndex ((== name) . getName . fieldName) (view #fields cf) of- Just i -> case f of- Nothing -> cf & over #fields (\v -> V.take i v <> V.drop (i + 1) v)- Just f' -> cf & over #fields (\v -> V.take i v <> V.singleton f' <> V.drop (i + 1) v)- Nothing -> cf & maybe id (over #fields . (\f -> (<> V.singleton f))) f+ Just i' -> case f of+ Nothing -> cf & over #fields (\v -> V.take i' v <> V.drop (i' + 1) v)+ Just f' -> cf & over #fields (\v -> V.take i' v <> V.singleton f' <> V.drop (i' + 1) v)+ Nothing -> cf & maybe id (over #fields . (\x -> (<> V.singleton x))) f -- | A lens by name into a field (but not a section). --@@ -633,7 +632,7 @@ -- >>> fs & toListOf (section' "test-suite" % each % secFields' % field' "build-depends" % each) & fmap (fixBuildDeps cfg "minimal") -- [Field (Name [] "build-depends") [FieldLine [] ", base >=4.14 && <5",FieldLine [] ", minimal"]] fixBuildDeps :: Config -> FieldName -> Field ann -> Field ann-fixBuildDeps cfg pname f = overField (bool id (over fieldLines' (fixBDLines cfg pname)) (isName "build-depends" f)) f+fixBuildDeps cfg pn f = overField (bool id (over fieldLines' (fixBDLines cfg pn)) (isName "build-depends" f)) f fixBDLines :: Config -> ByteString -> [FieldLine ann] -> [FieldLine ann] fixBDLines cfg libdep fls = fls'@@ -725,7 +724,7 @@ PP.vcat $ mconcat $ [ PP.text . fromUTF8BS <$> cs <> [bs]- | FieldLine cs bs <- fls+ | FieldLine cs bs <- fls ] -- | Used in 'fromParsecFields'.@@ -856,7 +855,7 @@ parseCabalFields cfg bs = case readFields bs of Left err -> Left $ C.pack (show err) Right fps ->- (\(fs, ec) -> Right (CabalFields (V.fromList fs) ec)) $+ (\(fs', ec) -> Right (CabalFields (V.fromList fs') ec)) $ foldl' (&) (fmap (fmap (const [])) fs, []) (uncurry addComment <$> cfs) where fs = convertFreeTexts (view #freeTexts cfg) fps@@ -925,7 +924,7 @@ makePositionTree :: [Field Position] -> Map.Map Int ([Int], String) makePositionTree fs = foldFss Map.empty [] fs where- foldFss m cursor fs = fst $ foldl' stepFss (m, cursor <> [0]) fs+ foldFss m cursor fs' = fst $ foldl' stepFss (m, cursor <> [0]) fs' stepFss (m, cursor) (Field (Name (Position c _) _) fls) = (foldFls (Map.insertWith (\_ o -> o) c (cursor, "fieldname") m) cursor fls, inc cursor) stepFss (m, cursor) (Section (Name (Position c _) _) sas fss) =@@ -944,7 +943,7 @@ addComment (Just (cursor, tag)) cs (fs, extras) = (addc cs cursor tag fs, extras) addc :: [ByteString] -> [Int] -> String -> [Field [ByteString]] -> [Field [ByteString]]-addc comments [] _ fs = fs+addc _ [] _ fs = fs addc comments [x] "fieldname" fs = take x fs <> [f'] <> drop (x + 1) fs where (Field (Name cs n) fls) = (List.!!) fs x
src/CabalFix/Archive.hs view
@@ -1,6 +1,5 @@ {-# LANGUAGE OverloadedLabels #-} {-# LANGUAGE OverloadedStrings #-}-{-# OPTIONS_GHC -Wno-name-shadowing #-} -- | Archive investigation module CabalFix.Archive where@@ -83,13 +82,13 @@ where libFields = cf & foldOf (#fields % fieldList' % section' "library" % each % secFields') libBds = libFields & foldOf (fieldValues' "build-depends")- libDeps = runParser_ (FP.many depP) libBds+ libDeps' = runParser_ (FP.many depP) libBds libImports = libFields & toListOf (fieldValues' "import") cs = cf & foldOf (#fields % fieldList' % section' "common") libCommons = cs & filter (all (`elem` libImports) . toListOf (secArgs' % each % secArgBS' % _2)) commonsBds = libCommons & foldOf (fieldValues' "build-depends") commonsDeps = runParser_ (FP.many depP) commonsBds- deps = fmap (uncurry Dep) (libDeps <> commonsDeps)+ deps = fmap (uncurry Dep) (libDeps' <> commonsDeps) -- | Map of valid dependencies validLibDeps :: Map.Map ByteString CabalFields -> Map.Map ByteString [ByteString]
+ test/doctests.hs view
@@ -0,0 +1,6 @@+import System.Environment (getArgs)+import Test.DocTest (mainFromCabal)+import Prelude (IO, (=<<))++main :: IO ()+main = mainFromCabal "cabal-fix" =<< getArgs