darcs-2.16.1: src/Darcs/Repository/Inventory.hs
module Darcs.Repository.Inventory
( Inventory(..)
, HeadInventory
, InventoryEntry
, ValidHash(..)
, InventoryHash
, PatchHash
, PristineHash
, inventoryPatchNames
, parseInventory
, parseHeadInventory -- not used
, showInventory
, showInventoryPatches
, showInventoryEntry
, emptyInventory
, pokePristineHash
, peekPristineHash
, skipPristineHash
, pristineName
-- properties
, prop_inventoryParseShow
, prop_peekPokePristineHash
, prop_skipPokePristineHash
) where
import Darcs.Prelude hiding ( take )
import Control.Applicative ( optional, many )
import Control.Monad ( guard )
import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as BC
import Darcs.Patch.Info ( PatchInfo, showPatchInfo, readPatchInfo )
import Darcs.Util.Parser
( Parser, parse, string, skipSpace, take, takeTillChar )
import Darcs.Patch.Show ( ShowPatchFor(..) )
import Darcs.Repository.Cache ( okayHash )
import Darcs.Util.Hash ( sha256sum )
import Darcs.Util.Printer
( Doc, (<+>), ($$), hcat, text, invisiblePS, packedString, renderPS )
-- * Hash validation
-- TODO the ValidHash class and the newtypes for the various hashes
-- really don't belong here. They should be moved to D.R.Cache or
-- perhaps a separate module. Also, the validation should be extended
-- see D.R.Cache.checkHash.
class ValidHash a where
getValidHash :: a -> String
mkValidHash :: String -> a
newtype InventoryHash = InventoryHash String
deriving (Eq, Show)
instance ValidHash InventoryHash where
getValidHash (InventoryHash h) = h
mkValidHash s
| okayHash s = InventoryHash s
| otherwise = error "Bad inventory hash!"
newtype PatchHash = PatchHash String
deriving (Eq, Show)
instance ValidHash PatchHash where
getValidHash (PatchHash h) = h
mkValidHash s
| okayHash s = PatchHash s
| otherwise = error "Bad patch hash!"
newtype PristineHash = PristineHash String
deriving (Eq, Show)
instance ValidHash PristineHash where
getValidHash (PristineHash h) = h
mkValidHash s
| okayHash s = PristineHash s
| otherwise = error "Bad pristine hash!"
-- * Inventories
-- This type and the parser combinators for it aren't actually used. They are
-- here to serve as documentation for the API we would like to use but won't
-- because of efficiency: we want to be able to access the pristine hash
-- without forcing a complete parse of the head inventory. Thus we retain the
-- lower-level peek/poke/skip API for the pristine hash.
type HeadInventory = (PristineHash, Inventory)
data Inventory = Inventory
{ inventoryParent :: Maybe InventoryHash
, inventoryPatches :: [InventoryEntry]
} deriving (Eq, Show)
-- The 'String' is the (hashed) patch filename.
type InventoryEntry = (PatchInfo, PatchHash)
inventoryPatchNames :: Inventory -> [String]
inventoryPatchNames = map (getValidHash . snd) . inventoryPatches
emptyInventory :: Inventory
emptyInventory = Inventory Nothing []
-- * Parsing
parseHeadInventory :: B.ByteString -> Either String HeadInventory
parseHeadInventory = fmap fst . parse pHeadInv
parseInventory :: B.ByteString -> Either String Inventory
parseInventory = fmap fst . parse pInv
pHeadInv :: Parser HeadInventory
pHeadInv = (,) <$> pPristineHash <*> pInv
pPristineHash :: Parser PristineHash
pPristineHash = do
string pristineName
skipSpace
pHash
pInv :: Parser Inventory
pInv = Inventory <$> pInvParent <*> pInvPatches
pInvParent :: Parser (Maybe InventoryHash)
pInvParent = optional $ do
string parentName
skipSpace
pHash
pHash :: ValidHash h => Parser h
pHash = do
hash <- BC.unpack <$> pLine
guard (okayHash hash)
return (mkValidHash hash)
pLine :: Parser B.ByteString
pLine = takeTillChar '\n' <* take 1
pInvPatches :: Parser [InventoryEntry]
pInvPatches = many pInvEntry
pInvEntry :: Parser InventoryEntry
pInvEntry = do
info <- readPatchInfo
skipSpace
string hashName
skipSpace
hash <- pHash
return (info, hash)
-- * Showing
showInventory :: Inventory -> Doc
showInventory inv =
showParent (inventoryParent inv) <>
showInventoryPatches (inventoryPatches inv)
showInventoryPatches :: [InventoryEntry] -> Doc
showInventoryPatches = hcat . map showInventoryEntry
showInventoryEntry :: InventoryEntry -> Doc
showInventoryEntry (pinf, hash) =
showPatchInfo ForStorage pinf $$
packedString hashName <+> text (getValidHash hash) <> packedString newline
showParent :: Maybe InventoryHash -> Doc
showParent (Just (InventoryHash hash)) =
packedString parentName $$ text hash <> packedString newline
showParent Nothing = mempty
-- * Accessing the pristine hash
-- | Replace the pristine hash at the start of a raw, unparsed 'HeadInventory'
-- or add it if none is present.
pokePristineHash :: PristineHash -> B.ByteString -> Doc
pokePristineHash (PristineHash h) inv =
invisiblePS pristineName <> text h $$ invisiblePS (skipPristineHash inv)
takeHash :: B.ByteString -> Maybe (String, B.ByteString)
takeHash input = do
let (hline,rest) = BC.breakSubstring newline input
let hash = BC.unpack hline
guard $ okayHash hash
return (hash, rest)
peekPristineHash :: B.ByteString -> PristineHash
peekPristineHash inv =
case tryDropPristineName inv of
Just rest ->
case takeHash rest of
Just (h, _) -> mkValidHash h
Nothing -> error $ "Bad hash in inventory!"
Nothing -> mkValidHash $ sha256sum B.empty
-- |skipPristineHash drops the 'pristine: HASH' prefix line, if present.
skipPristineHash :: B.ByteString -> B.ByteString
skipPristineHash ps =
case tryDropPristineName ps of
Just rest -> B.drop 1 $ BC.dropWhile (/= '\n') rest
Nothing -> ps
tryDropPristineName :: B.ByteString -> Maybe B.ByteString
tryDropPristineName input =
if prefix == pristineName then Just rest else Nothing
where
(prefix, rest) = B.splitAt (B.length pristineName) input
-- * Key phrases
pristineName :: B.ByteString
pristineName = BC.pack "pristine:"
parentName :: B.ByteString
parentName = BC.pack "Starting with inventory:"
hashName :: B.ByteString
hashName = BC.pack "hash:"
newline :: B.ByteString
newline = BC.pack "\n"
-- * Properties
prop_inventoryParseShow :: Inventory -> Bool
prop_inventoryParseShow inv =
Right inv == parseInventory (renderPS (showInventory inv))
prop_peekPokePristineHash :: (PristineHash, B.ByteString) -> Bool
prop_peekPokePristineHash (hash, raw) =
hash == peekPristineHash (renderPS (pokePristineHash hash raw))
prop_skipPokePristineHash :: (PristineHash, B.ByteString) -> Bool
prop_skipPokePristineHash (hash, raw) =
raw == skipPristineHash (renderPS (pokePristineHash hash raw))