{-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedStrings #-} -- | -- Copyright : Herbert Valerio Riedel -- SPDX-License-Identifier: GPL-3.0-or-later module CabalEdit ( cabalEditXRev , cabalSplitAtField , PkgRev ) where import Data.ByteString (ByteString) import qualified Data.ByteString.Char8 as BS8 #if !MIN_VERSION_base(4,11,0) import Data.Semigroup ((<>)) #endif import qualified Distribution.Fields.Field as C import qualified Distribution.Fields.Parser as C import qualified Distribution.Parsec.Position as C (Position(Position)) type PkgRev = Word -- | Insert or replace existing "x-revision" line -- -- __NOTE__: This uses 'cabalSplitAtField' and therefore currently -- supports only simplified (i.e. without use of @{@ @}@ layout -- tokens) Cabal file grammar cabalEditXRev :: PkgRev -> ByteString -> ByteString cabalEditXRev xrev oldcab = pre1 <> mid1 <> xrevLine <> post1 where (pre0,_,post0) = either (error . show) id $ cabalSplitAtField "x-revision" oldcab (pre1,mid1,post1) = either (error . show) id $ cabalSplitAtField "version" (pre0 <> post0) ls = BS8.lines oldcab xrevLine = "x-revision: " <> BS8.pack (show xrev) <> if isCRLF then "\r\n" else "\n" -- simple heuristic isCRLF = case ls of [] -> False ("":_) -> False (l1:_) -> BS8.last l1 == '\r' -- | Split a cabal file into three fragments: before the (first -- occurence of a field), the field, and the rest after the field. -- -- This should, in spirit, follow the invariant (assuming there are no parsing failures): -- -- > cabalSplitAtField someField (pre <> mid <> post) == Right (pre, mid, post) -- -- If field not found, the middle and trailing fragment will be empty 'ByteString's -- -- This operation is quite universal; it can be used to remove fields, -- or insert content before a field or after a field etc -- -- __NOTE__: This doesn't properly handle layout-mode (i.e. as controlled by the @{@ and @}@ tokens) yet cabalSplitAtField :: ByteString -- ^ fieldname -> ByteString -- ^ @.cabal@ description -> Either String (ByteString,ByteString,ByteString) -- ^ pre, mid, post cabalSplitAtField fname cab0 = do fields <- either (Left . show) Right $ C.readFields cab0 case [ (npos, vals) | C.Field (C.Name (C.Position npos _) n) vals <- fields, n == fname ] of [] -> pure (cab0, BS8.empty, BS8.empty) (npos, vals):_ -> case vals of [] -> pure $! go (npos-1) 1 _ -> let C.FieldLine (C.Position pos2 _) _ = last vals in pure $! go (npos-1) (1+pos2-npos) where cab0lines = BS8.lines cab0 -- TODO: detect '{'s in `mid` and fixup k to include the closing '}' -- -- NB: it's not enough to simply look at the next field's line -- number, as we don't want 'mid' to include trailing comments -- which conventionally belong to the next fields; i.e. in case of -- something like -- -- name: foo -- version: 1.2.3 -- x-revision: { -- 42 -- } -- -- for reasons this must remain Custom instead of Simple -- build-type: Custm -- synopsis: ... -- -- We want the middle of 'cabalSplitAtField "x-revision"' to stop -- right after the '}' and *not* include the comment -- go j k = (BS8.unlines pre, BS8.unlines mid, BS8.unlines post) where (pre, midpost) = splitAt j cab0lines (mid,post) = splitAt k midpost