-- | Some generic operations with specializations to avoid broken Data -- instances in types like Text and Set. {-# LANGUAGE DeriveDataTypeable, RankNTypes, StandaloneDeriving #-} {-# OPTIONS_GHC -fno-warn-orphans #-} module Debian.Debianize.Generic ( geq , gdiff , gshow ) where import Prelude hiding (GT) import Data.Generics (Data, Typeable, GenericQ, toConstr, showConstr, gzipWithQ, extQ, ext1Q, gmapQ, Constr {- , ext2Q, Typeable2, dataTypeName, dataTypeOf-}) import Data.List (sort) import qualified Data.Text as T import Data.Set as Set (Set, toList, fromList, difference) import Debian.Debianize.Atoms (Atoms) import Debian.Debianize.ControlFile (VersionControlSpec, XField) import Debian.Debianize.Utility (showDeps) import Debian.Relation (Relation) import Triplets (mkQ2, extQ2) -- These instances are only used here, to create debugging messages. deriving instance Typeable Atoms -- deriving instance Data Debianization -- deriving instance Data DebAtom -- ext2Q' :: (Data d, Typeable2 t) => (d -> q) -> (forall d1 d2. (Data d1, Data d2) => t d1 d2 -> q) -> d -> q -- ext2Q' = ext2Q geq :: GenericQ (GenericQ Bool) geq x y = (geq' `mkQ2` stringEq `extQ2` textEq `extQ2` setEq1 `extQ2` setEq2 `extQ2` mapEq1) x y where -- If the specialized eqs don't work, use the generic. This -- will throw an exception if it encounters something with a -- NoRep type. geq' :: (Data a, Data b) => a -> b -> Bool geq' x' y' = (toConstr x' == toConstr y') && and (gzipWithQ geq x' y') stringEq :: String -> String -> Bool stringEq a b = (a == b) textEq :: T.Text -> T.Text -> Bool textEq a b = (a == b) setEq1 :: Set VersionControlSpec -> Set VersionControlSpec -> Bool setEq1 a b = toList a == toList b setEq2 :: Set XField -> Set XField -> Bool setEq2 a b = toList a == toList b mapEq1 :: Atoms -> Atoms -> Bool mapEq1 a b = (a == b) data Diff = Diff { stack :: [Constr], expected :: String, actual :: String } | SetDiff { stack :: [Constr], expected :: String, missing :: String, extra :: String } deriving (Eq, Show) gdiff :: GenericQ (GenericQ [Diff]) gdiff x y = (gdiff' `mkQ2` stringEq `extQ2` textEq `extQ2` setEq1 `extQ2` setEq2 `extQ2` mapEq1 `extQ2` relEq) x y where -- If the specialized eqs don't work, use the generic. This -- will throw an exception if it encounters something with a -- NoRep type. gdiff' :: (Data a, Data b) => a -> b -> [Diff] gdiff' x' y' = if toConstr x' == toConstr y' then map (\ diff -> diff {stack = toConstr x' : stack diff}) (concat (gzipWithQ gdiff x' y')) else [Diff {stack = [], expected = gshow x', actual = gshow y'}] stringEq :: String -> String -> [Diff] stringEq a b = if (a == b) then [] else [Diff {stack = [], expected = show a, actual = show b}] textEq :: T.Text -> T.Text -> [Diff] textEq a b = if a == b then [] else [Diff {stack = [], expected = show a, actual = show b}] setEq1 :: Set VersionControlSpec -> Set VersionControlSpec -> [Diff] setEq1 a b = if a == b then [] else [Diff {stack = [], expected = show a, actual = show b}] setEq2 :: Set XField -> Set XField -> [Diff] setEq2 a b = if a == b then [] else [Diff {stack = [], expected = show a, actual = show b}] mapEq1 :: Atoms -> Atoms -> [Diff] mapEq1 a b = if a == b then [] else [Diff {stack = [], expected = show a, actual = show b}] relEq :: [[Relation]] -> [[Relation]] -> [Diff] relEq a b = if Set.fromList a == Set.fromList b then [] else [SetDiff {stack = [], expected = show [a, b], missing = showDeps (sort (Set.toList (Set.difference (Set.fromList a) (Set.fromList b)))), extra = showDeps (sort (Set.toList (Set.difference (Set.fromList b) (Set.fromList a))))}] {- gshow' :: Data a => a -> String gshow' x = (gshow `extQ` (show :: T.Text -> String) `extQ` (show :: Maybe T.Text -> String)) x -} -- | Generic show: an alternative to \"deriving Show\" gshow :: Data a => a -> String gshow x = gshows x "" -- | Generic shows gshows :: Data a => a -> ShowS -- This is a prefix-show using surrounding "(" and ")", -- where we recurse into subterms with gmapQ. gshows = ( \t -> showChar '(' . (showString . showConstr . toConstr $ t) . (foldr (.) id . gmapQ ((showChar ' ' .) . gshows) $ t) . showChar ')' ) `extQ` (shows :: String -> ShowS) `extQ` ((shows . T.unpack) :: T.Text -> ShowS) `ext1Q` (\ s -> gshows (toList s))