Safe Haskell | Safe-Inferred |
---|---|
Language | GHC2021 |
Tools to print, parse and fix cabal files, ByteString
and Field
lists.
Synopsis
- data Config = Config {
- freeTexts :: [ByteString]
- fieldRemovals :: [ByteString]
- preferredDeps :: [(ByteString, ByteString)]
- addFields :: [(ByteString, ByteString, AddPolicy)]
- fixCommas :: [(ByteString, CommaStyle, CommaTrail)]
- sortFieldLines :: [ByteString]
- doSortFields :: Bool
- fieldOrdering :: [(ByteString, Double)]
- doFixBuildDeps :: Bool
- depAlignment :: DepAlignment
- removeBlankFields :: Bool
- valueAligned :: ValueAlignment
- valueAlignGap :: Int
- sectionMargin :: Margin
- commentMargin :: Margin
- narrowN :: Int
- indentN :: Int
- defaultConfig :: Config
- data AddPolicy
- data CommaStyle
- data CommaTrail
- data DepAlignment
- data ValueAlignment
- data Margin
- type Comment = [ByteString]
- data CabalFields = CabalFields {}
- cabalFields' :: Config -> Prism' ByteString CabalFields
- fieldList' :: Iso' (Vector (Field Comment)) [Field Comment]
- topfield' :: FieldName -> Lens' CabalFields (Maybe (Field Comment))
- field' :: FieldName -> Getter [Field Comment] [Field Comment]
- subfield' :: FieldName -> Getter (Field Comment) [Field Comment]
- section' :: FieldName -> Getter [Field ann] [Field ann]
- secFields' :: Lens' (Field ann) [Field ann]
- fieldOrSection' :: FieldName -> Getter [Field ann] [Field ann]
- overField :: (Field ann -> Field ann) -> Field ann -> Field ann
- overFields :: ([Field ann] -> [Field ann]) -> [Field ann] -> [Field ann]
- pname :: CabalFields -> ByteString
- fieldLines' :: Lens' (Field ann) [FieldLine ann]
- fieldName' :: Lens' (Field ann) ByteString
- secArgs' :: Lens' (Field ann) [SectionArg ann]
- secArgBS' :: Lens' (SectionArg ann) (ByteString, ByteString)
- fieldLine' :: Lens' (FieldLine ann) ByteString
- fieldValues' :: FieldName -> Optic A_Fold '[Int, Int] [Field Comment] [Field Comment] ByteString ByteString
- parseCabalFields :: Config -> ByteString -> Either ByteString CabalFields
- printCabalFields :: Config -> CabalFields -> ByteString
- fixCabalFields :: Config -> CabalFields -> CabalFields
- fixCabalFile :: FilePath -> Config -> IO Bool
- fixesCommas :: Config -> Field ann -> Field ann
- addsFields :: Config -> [Field Comment] -> [Field Comment]
- addField :: AddPolicy -> Field ann -> [Field ann] -> [Field ann]
- fixBuildDeps :: Config -> FieldName -> Field ann -> Field ann
- data Dep = Dep {
- dep :: ByteString
- depRange :: ByteString
- minimalExampleBS :: ByteString
- minimalConfig :: Config
Usage
>>>
:set -XOverloadedStrings
>>>
:set -XOverloadedLabels
>>>
import CabalFix
>>>
import Optics.Extra
>>>
import Data.ByteString.Char8 qualified as C
>>>
import CabalFix.Patch
>>>
bs = minimalExampleBS
>>>
cfg = defaultConfig
>>>
(Just cf) = preview (cabalFields' cfg) bs
>>>
fs = cf & view (#fields % fieldList')
>>>
printCabalFields cfg (cf & over (#fields % fieldList') (take 4)) & C.putStr
cabal-version: 3.0 name: minimal version: 0.1.0.0 license: BSD-2-Clause
Configuration
Configuration values for various aspects of (re)rendering a cabal file.
Config | |
|
Instances
defaultConfig :: Config Source #
An opinionated configuration for formatting cabal files.
Some opinions (that can be configured):
>>>
fixCommas defaultConfig
[("extra-doc-files",NoCommas,NoTrailer),("build-depends",PrefixCommas,Trailer)]
PrefixCommas
are better for the dependency list as dependency ranges are already noisy enough without a comma thrown in. Trailer
(which means leading comma for prefixed commas) is neater and easier to prepend to, append to & sort.
If a field list doesn't need commas, then they should be removed.
>>>
preferredDeps defaultConfig
[("base",">=4.17 && <5")]
Standard practice compared with the much tighter eg base ^>=4.17.2.1
>>>
sortFieldLines defaultConfig
["build-depends","exposed-modules","default-extensions","ghc-options","extra-doc-files","tested-with"]
Sort all the things, but especially the module list.
>>>
valueAligned defaultConfig
ValueUnaligned
Adding an extra, long-named field to the cabal file means we have to re-align all the value parts in all the other fields.
>>>
depAlignment defaultConfig
DepAligned
build-depends is so busy, however, the extra alignment becomes more important.
>>>
doSortFields defaultConfig
True
Whatever the order, fields should have the same order within each section.
Policy for Fields listed in addFields
AddReplace | Replace existing values |
AddAppend | Append after existing values |
AddIfNotExisting | Add only of the Field doesn't exist |
Instances
Generic AddPolicy Source # | |
Read AddPolicy Source # | |
Show AddPolicy Source # | |
Eq AddPolicy Source # | |
type Rep AddPolicy Source # | |
Defined in CabalFix type Rep AddPolicy = D1 ('MetaData "AddPolicy" "CabalFix" "cabal-fix-0.0.0.1-Lbi9cRjrdFyLDBrkksBDMb" 'False) (C1 ('MetaCons "AddReplace" 'PrefixI 'False) (U1 :: Type -> Type) :+: (C1 ('MetaCons "AddAppend" 'PrefixI 'False) (U1 :: Type -> Type) :+: C1 ('MetaCons "AddIfNotExisting" 'PrefixI 'False) (U1 :: Type -> Type))) |
data CommaStyle Source #
The style for comma-separated values
PrefixCommas | commas before values |
PostfixCommas | commas after values |
FreeformCommas | comma freedom |
NoCommas | remove commas (allowed for some fields) |
Instances
data CommaTrail Source #
Include a trailing (or leading) comma, after the last value (or before the first value.)
Instances
Generic CommaTrail Source # | |
Defined in CabalFix type Rep CommaTrail :: Type -> Type # from :: CommaTrail -> Rep CommaTrail x # to :: Rep CommaTrail x -> CommaTrail # | |
Read CommaTrail Source # | |
Defined in CabalFix readsPrec :: Int -> ReadS CommaTrail # readList :: ReadS [CommaTrail] # readPrec :: ReadPrec CommaTrail # readListPrec :: ReadPrec [CommaTrail] # | |
Show CommaTrail Source # | |
Defined in CabalFix showsPrec :: Int -> CommaTrail -> ShowS # show :: CommaTrail -> String # showList :: [CommaTrail] -> ShowS # | |
Eq CommaTrail Source # | |
Defined in CabalFix (==) :: CommaTrail -> CommaTrail -> Bool # (/=) :: CommaTrail -> CommaTrail -> Bool # | |
type Rep CommaTrail Source # | |
data DepAlignment Source #
Whether the range part of the dependency list should be vertically aligned on a column.
Instances
Read DepAlignment Source # | |
Defined in CabalFix readsPrec :: Int -> ReadS DepAlignment # readList :: ReadS [DepAlignment] # | |
Show DepAlignment Source # | |
Defined in CabalFix showsPrec :: Int -> DepAlignment -> ShowS # show :: DepAlignment -> String # showList :: [DepAlignment] -> ShowS # | |
Eq DepAlignment Source # | |
Defined in CabalFix (==) :: DepAlignment -> DepAlignment -> Bool # (/=) :: DepAlignment -> DepAlignment -> Bool # |
data ValueAlignment Source #
Whether the value part of each field should be vertically aligned on a column.
Instances
Generic ValueAlignment Source # | |
Defined in CabalFix type Rep ValueAlignment :: Type -> Type # from :: ValueAlignment -> Rep ValueAlignment x # to :: Rep ValueAlignment x -> ValueAlignment # | |
Read ValueAlignment Source # | |
Defined in CabalFix readsPrec :: Int -> ReadS ValueAlignment # readList :: ReadS [ValueAlignment] # | |
Show ValueAlignment Source # | |
Defined in CabalFix showsPrec :: Int -> ValueAlignment -> ShowS # show :: ValueAlignment -> String # showList :: [ValueAlignment] -> ShowS # | |
Eq ValueAlignment Source # | |
Defined in CabalFix (==) :: ValueAlignment -> ValueAlignment -> Bool # (/=) :: ValueAlignment -> ValueAlignment -> Bool # | |
type Rep ValueAlignment Source # | |
A margin tracker for combining sections.
CabalFields
type Comment = [ByteString] Source #
Note that cabal does not have multi-line comments
data CabalFields Source #
Field
list annotated with a Comment
Note that this type does not contain any position information.
The construction assumes that comments relate to fields below, so there is potential for an end comment unrelated to any particular field.
Instances
cabalFields' :: Config -> Prism' ByteString CabalFields Source #
A Prism betwixt a ByteString
and a CabalFields
.
>>>
cf & over (#fields % fieldList') (take 2) & review (cabalFields' cfg) & C.putStr
cabal-version: 3.0 name: minimal
fieldList' :: Iso' (Vector (Field Comment)) [Field Comment] Source #
iso to flip between vectors and lists easily.
>>>
cf & view (#fields % fieldList') & take 2
[Field (Name [] "cabal-version") [FieldLine [] "3.0"],Field (Name [] "name") [FieldLine [] "minimal"]]
Lenses
Lensing into Field
is tricky.
A Field
is a sum type of a field constructor or a section constructor, and a section contains fields.
Sometimes you only want to modify a field (and not a section). Other times you want to access a section but not a field. Sometimes you want to modify either a field or a section, and the fields within sections. It can be difficult to remember which lens is which.
The use of a list is also problematic; it is hard to safely delete a field, and invalid cabals are easily represented. A list can easily contain two name fields say, which is an invalid state. It can contain no name which is also invalid. It is difficult, however, to switch to a map because sections contain lists of fields (and not maps of fields).
Most useful are lenses that lens into named fields.
topfield' :: FieldName -> Lens' CabalFields (Maybe (Field Comment)) Source #
A lens that doesn't descend into sections. It will lens the first-encountered named field, if any.
>>>
view (topfield' "name") cf
Just (Field (Name [] "name") [FieldLine [] "minimal"])
>>>
view (topfield' "build-depends") cf
Nothing
field' :: FieldName -> Getter [Field Comment] [Field Comment] Source #
A lens by name into a field (but not a section).
>>>
fs & view (field' "version")
[Field (Name [] "version") [FieldLine [] "0.1.0.0"]]
subfield' :: FieldName -> Getter (Field Comment) [Field Comment] Source #
A getter by name into a field (including within sections)
>>>
fs & toListOf (each % subfield' "default-language")
[[],[],[],[],[],[],[],[],[Field (Name [] "default-language") [FieldLine [] "GHC2021"]],[Field (Name [] "default-language") [FieldLine [] "GHC2021"]]]
section' :: FieldName -> Getter [Field ann] [Field ann] Source #
A getter of a section (not a field)
>>>
fs & foldOf (section' "library" % each % secFields' % field' "exposed-modules")
[Field (Name [] "exposed-modules") [FieldLine [] "MyLib"]]
fieldOrSection' :: FieldName -> Getter [Field ann] [Field ann] Source #
A getter by name of a field or section.
overField :: (Field ann -> Field ann) -> Field ann -> Field ann Source #
A mapping into the field structure, operating on field lists in sections as well as the field itself.
overFields :: ([Field ann] -> [Field ann]) -> [Field ann] -> [Field ann] Source #
A mapping into the field structure, operating on field lists in sections as well as field lists themselves.
pname :: CabalFields -> ByteString Source #
Project name. Errors if the field is missing.
>>>
pname cf
"minimal"
fieldLines' :: Lens' (Field ann) [FieldLine ann] Source #
Lens into field lines
>>>
fs & foldOf (section' "test-suite" % each % secFields' % field' "build-depends" % each % fieldLines')
[FieldLine [] "base ^>=4.17.2.1,",FieldLine [] "minimal"]
fieldName' :: Lens' (Field ann) ByteString Source #
Name of (field or section).
>>>
head fs & view fieldName'
"cabal-version"
secArgs' :: Lens' (Field ann) [SectionArg ann] Source #
lens into SectionArg part of a section.
Errors if you actually have a field.
>>>
fs & foldOf (section' "test-suite" % each % secArgs')
[SecArgName [] "minimal-test"]
secArgBS' :: Lens' (SectionArg ann) (ByteString, ByteString) Source #
secArg lens into a ByteString representation
>>>
fs & foldOf (section' "test-suite" % each % secArgs' % each % secArgBS')
("name","minimal-test")
fieldLine' :: Lens' (FieldLine ann) ByteString Source #
lens into field line contents.
>>>
fs & toListOf (section' "test-suite" % each % secFields' % field' "build-depends" % each % fieldLines' % each % fieldLine')
["base ^>=4.17.2.1,","minimal"]
fieldValues' :: FieldName -> Optic A_Fold '[Int, Int] [Field Comment] [Field Comment] ByteString ByteString Source #
A fold of a field list into a ByteString.
Parsing
parseCabalFields :: Config -> ByteString -> Either ByteString CabalFields Source #
Parse a ByteString
into a CabalFields
. Failure is possible.
>>>
bs & C.lines & take 4 & C.unlines & parseCabalFields cfg
Right (CabalFields {fields = [Field (Name [] "cabal-version") [FieldLine [] "3.0"],Field (Name [] "name") [FieldLine [] "minimal"],Field (Name [] "version") [FieldLine [] "0.1.0.0"],Field (Name [] "license") [FieldLine [] "BSD-2-Clause"]], endComment = []})
Printing
printCabalFields :: Config -> CabalFields -> ByteString Source #
Printing
Convert a CabalFields
to a ByteString
>>>
printCabalFields cfg (cf & over (#fields % fieldList') (take 4)) & C.putStr
cabal-version: 3.0 name: minimal version: 0.1.0.0 license: BSD-2-Clause
Fixes
fixCabalFields :: Config -> CabalFields -> CabalFields Source #
fix order:
- removes fields
- removes blank fields
- fixes commas
- adds Fields
- fix build dependencies
- sort field lines
- sort fields
fixesCommas :: Config -> Field ann -> Field ann Source #
Fix the comma usage in a field list
>>>
fs & toListOf (section' "test-suite" % each % secFields' % field' "build-depends" % each) & fmap (fixesCommas cfg)
[Field (Name [] "build-depends") [FieldLine [] ", base ^>=4.17.2.1",FieldLine [] ", minimal"]]
addsFields :: Config -> [Field Comment] -> [Field Comment] Source #
add fields
>>>
addsFields (cfg & set #addFields [("description", "added by addsFields", AddReplace)]) []
[Field (Name [] "description") [FieldLine [] "added by addsFields"]]
addField :: AddPolicy -> Field ann -> [Field ann] -> [Field ann] Source #
Add a field according to an AddPolicy.
fixBuildDeps :: Config -> FieldName -> Field ann -> Field ann Source #
Align dependencies (if depAlignment is DepAligned), remove ranges for any self-dependency, and substitute preferred dependency ranges.
>>>
fs & toListOf (section' "test-suite" % each % secFields' % field' "build-depends" % each) & fmap (fixBuildDeps cfg "minimal")
[Field (Name [] "build-depends") [FieldLine [] ", base >=4.17 && <5",FieldLine [] ", minimal"]]
Dependency
Split of a dependency FieldLine
into the dependency name and the range.
Dep | |
|
Instances
Generic Dep Source # | |
Show Dep Source # | |
Eq Dep Source # | |
Ord Dep Source # | |
type Rep Dep Source # | |
Defined in CabalFix type Rep Dep = D1 ('MetaData "Dep" "CabalFix" "cabal-fix-0.0.0.1-Lbi9cRjrdFyLDBrkksBDMb" 'False) (C1 ('MetaCons "Dep" 'PrefixI 'True) (S1 ('MetaSel ('Just "dep") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 ByteString) :*: S1 ('MetaSel ('Just "depRange") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 ByteString))) |
Examples
minimalExampleBS :: ByteString Source #
Minimal cabal file contents for testing purposes. Originally created via:
mkdir minimal && cd minimal && cabal init --minimal --simple --overwrite --lib --tests --language=GHC2021 --license=BSD-2-Clause -p minimal
minimalConfig :: Config Source #
A config close to the cabal init
styles.