    {-|

    Instances for anonymizing sensitive data in various types.

    Note that there is no clear way to anonymize numbers.

    -}

    module Hledger.Cli.Anon
        ( Anon(..)
        , anonAccount
        )
    where

    import Control.Arrow (first)
    import Data.Hashable (hash)
    import Data.Word (Word32)
    import Numeric (showHex)
    import qualified Data.Text as T

    import Hledger.Data
    import Data.Map (mapKeys)

    class Anon a where
        -- | Consistent converter to structure with sensitive data anonymized
        anon :: a -> a

    instance Anon Journal where
        -- Apply the anonymisation transformation on a journal after finalisation
        anon :: Journal -> Journal
    anon Journal
    j = Journal
    j { jtxns :: [Transaction]
    jtxns = (Transaction -> Transaction) -> [Transaction] -> [Transaction]
    forall a b. (a -> b) -> [a] -> [b]
    map Transaction -> Transaction
    forall a. Anon a => a -> a
    anon ([Transaction] -> [Transaction])
    -> (Journal -> [Transaction]) -> Journal -> [Transaction]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Journal -> [Transaction]
    jtxns (Journal -> [Transaction]) -> Journal -> [Transaction]
    forall a b. (a -> b) -> a -> b
    $ Journal
    j
                   , jparseparentaccounts :: [Text]
    jparseparentaccounts  = (Text -> Text) -> [Text] -> [Text]
    forall a b. (a -> b) -> [a] -> [b]
    map Text -> Text
    anonAccount ([Text] -> [Text]) -> [Text] -> [Text]
    forall a b. (a -> b) -> a -> b
    $ Journal -> [Text]
    jparseparentaccounts Journal
    j
                   , jparsealiases :: [AccountAlias]
    jparsealiases         = []  -- already applied
                   , jdeclaredaccounts :: [(Text, AccountDeclarationInfo)]
    jdeclaredaccounts     = ((Text, AccountDeclarationInfo) -> (Text, AccountDeclarationInfo))
    -> [(Text, AccountDeclarationInfo)]
    -> [(Text, AccountDeclarationInfo)]
    forall a b. (a -> b) -> [a] -> [b]
    map ((Text -> Text)
    -> (Text, AccountDeclarationInfo) -> (Text, AccountDeclarationInfo)
    forall b c d. (b -> c) -> (b, d) -> (c, d)
    forall (a :: * -> * -> *) b c d.
    Arrow a =>
    a b c -> a (b, d) (c, d)
    first Text -> Text
    forall a. Anon a => a -> a
    anon) ([(Text, AccountDeclarationInfo)]
     -> [(Text, AccountDeclarationInfo)])
    -> [(Text, AccountDeclarationInfo)]
    -> [(Text, AccountDeclarationInfo)]
    forall a b. (a -> b) -> a -> b
    $ Journal -> [(Text, AccountDeclarationInfo)]
    jdeclaredaccounts Journal
    j
                   , jdeclaredaccounttags :: Map Text [Tag]
    jdeclaredaccounttags  = (Text -> Text) -> Map Text [Tag] -> Map Text [Tag]
    forall k2 k1 a. Ord k2 => (k1 -> k2) -> Map k1 a -> Map k2 a
    mapKeys Text -> Text
    forall a. Anon a => a -> a
    anon (Map Text [Tag] -> Map Text [Tag])
    -> Map Text [Tag] -> Map Text [Tag]
    forall a b. (a -> b) -> a -> b
    $ Journal -> Map Text [Tag]
    jdeclaredaccounttags Journal
    j
                   , jdeclaredaccounttypes :: Map AccountType [Text]
    jdeclaredaccounttypes = ((Text -> Text) -> [Text] -> [Text]
    forall a b. (a -> b) -> [a] -> [b]
    map Text -> Text
    forall a. Anon a => a -> a
    anon) ([Text] -> [Text])
    -> Map AccountType [Text] -> Map AccountType [Text]
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> Journal -> Map AccountType [Text]
    jdeclaredaccounttypes Journal
    j
                   }

    instance Anon Posting where
        anon :: Posting -> Posting
    anon Posting
    p = Posting
    p { paccount :: Text
    paccount = Text -> Text
    anonAccount (Text -> Text) -> (Posting -> Text) -> Posting -> Text
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Posting -> Text
    paccount (Posting -> Text) -> Posting -> Text
    forall a b. (a -> b) -> a -> b
    $ Posting
    p
                   , pcomment :: Text
    pcomment = Text
    T.empty
                   , ptransaction :: Maybe Transaction
    ptransaction = (Transaction -> Transaction)
    -> Maybe Transaction -> Maybe Transaction
    forall a b. (a -> b) -> Maybe a -> Maybe b
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    fmap Transaction -> Transaction
    forall a. Anon a => a -> a
    anon (Maybe Transaction -> Maybe Transaction)
    -> (Posting -> Maybe Transaction) -> Posting -> Maybe Transaction
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Posting -> Maybe Transaction
    ptransaction (Posting -> Maybe Transaction) -> Posting -> Maybe Transaction
    forall a b. (a -> b) -> a -> b
    $ Posting
    p  -- Note that this will be overridden
                   , poriginal :: Maybe Posting
    poriginal = Posting -> Posting
    forall a. Anon a => a -> a
    anon (Posting -> Posting) -> Maybe Posting -> Maybe Posting
    forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
    <$> Posting -> Maybe Posting
    poriginal Posting
    p
                   }

    instance Anon Transaction where
        anon :: Transaction -> Transaction
    anon Transaction
    txn = Transaction -> Transaction
    txnTieKnot (Transaction -> Transaction) -> Transaction -> Transaction
    forall a b. (a -> b) -> a -> b
    $ Transaction
    txn { tpostings :: [Posting]
    tpostings = (Posting -> Posting) -> [Posting] -> [Posting]
    forall a b. (a -> b) -> [a] -> [b]
    map Posting -> Posting
    forall a. Anon a => a -> a
    anon ([Posting] -> [Posting])
    -> (Transaction -> [Posting]) -> Transaction -> [Posting]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Transaction -> [Posting]
    tpostings (Transaction -> [Posting]) -> Transaction -> [Posting]
    forall a b. (a -> b) -> a -> b
    $ Transaction
    txn
                                    , tdescription :: Text
    tdescription = Text -> Text
    forall a. Anon a => a -> a
    anon (Text -> Text) -> (Transaction -> Text) -> Transaction -> Text
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Transaction -> Text
    tdescription (Transaction -> Text) -> Transaction -> Text
    forall a b. (a -> b) -> a -> b
    $ Transaction
    txn
                                    , tcode :: Text
    tcode = Text -> Text
    forall a. Anon a => a -> a
    anon (Text -> Text) -> (Transaction -> Text) -> Transaction -> Text
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Transaction -> Text
    tcode (Transaction -> Text) -> Transaction -> Text
    forall a b. (a -> b) -> a -> b
    $ Transaction
    txn
                                    , tcomment :: Text
    tcomment = Text
    T.empty
                                    }

    -- | Anonymize account name preserving hierarchy
    anonAccount :: AccountName -> AccountName
    anonAccount :: Text -> Text
    anonAccount = Text -> [Text] -> Text
    T.intercalate (String -> Text
    T.pack String
    ":") ([Text] -> Text) -> (Text -> [Text]) -> Text -> Text
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Text -> Text) -> [Text] -> [Text]
    forall a b. (a -> b) -> [a] -> [b]
    map Text -> Text
    forall a. Anon a => a -> a
    anon ([Text] -> [Text]) -> (Text -> [Text]) -> Text -> [Text]
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . HasCallStack => Text -> Text -> [Text]
    Text -> Text -> [Text]
    T.splitOn (String -> Text
    T.pack String
    ":")

    instance Anon T.Text where anon :: Text -> Text
    anon = String -> Text
    T.pack (String -> Text) -> (Text -> String) -> Text -> Text
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Word32 -> String -> String) -> String -> Word32 -> String
    forall a b c. (a -> b -> c) -> b -> a -> c
    flip Word32 -> String -> String
    forall a. Integral a => a -> String -> String
    showHex String
    "" (Word32 -> String) -> (Text -> Word32) -> Text -> String
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . (Int -> Word32
    forall a b. (Integral a, Num b) => a -> b
    fromIntegral :: Int -> Word32) (Int -> Word32) -> (Text -> Int) -> Text -> Word32
    forall b c a. (b -> c) -> (a -> b) -> a -> c
    . Text -> Int
    forall a. Hashable a => a -> Int
    hash