{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE FlexibleContexts #-}
module Nix.Diff.Transformations where

import qualified Patience
import Data.Generics.Uniplate.Data ( transformBi )

import Nix.Diff.Types

{-| In large diffs there may be a lot of derivations
    that doesn't change at all, but changed some of
    its nested inputs, that was already compared.
    This case will produce "stairs" of useless reports:
    ```
    • The input derivation named `a` differs
      - /nix/store/j1jmbxd74kzianaywml2nw1ja31a00r5-a.drv:{out}
      + /nix/store/ww51c2dha7m5l5qjzh2rblicsamkrh62-a.drv:{out}
      • The input derivation named `b` differs
        - /nix/store/j1jmbxd74kzianaywml2nw1ja31a00r5-b.drv:{out}
        + /nix/store/ww51c2dha7m5l5qjzh2rblicsamkrh62-b.drv:{out}
        • The input derivation named `c` differs
          • These two derivations have already been compared
    ```
    This transformation will fold all these subtrees of diff
    into one OnlyAlreadComparedBelow.
-}
foldAlreadyComparedSubTrees :: DerivationDiff -> DerivationDiff
foldAlreadyComparedSubTrees :: DerivationDiff -> DerivationDiff
foldAlreadyComparedSubTrees DerivationDiff
dd = case DerivationDiff
dd of
  DerivationDiff
DerivationsAreTheSame -> DerivationDiff
dd
  DerivationDiff
AlreadyCompared -> DerivationDiff
dd
  OnlyAlreadyComparedBelow{} -> DerivationDiff
dd
  NamesDontMatch{} -> DerivationDiff
dd
  OutputsDontMatch{} -> DerivationDiff
dd
  DerivationDiff{Maybe EnvironmentDiff
Maybe ArgumentsDiff
Maybe (Changed Text)
InputsDiff
SourcesDiff
OutputsDiff
Changed OutputStructure
outputStructure :: Changed OutputStructure
outputsDiff :: OutputsDiff
platformDiff :: Maybe (Changed Text)
builderDiff :: Maybe (Changed Text)
argumentsDiff :: Maybe ArgumentsDiff
sourcesDiff :: SourcesDiff
inputsDiff :: InputsDiff
envDiff :: Maybe EnvironmentDiff
outputStructure :: DerivationDiff -> Changed OutputStructure
outputsDiff :: DerivationDiff -> OutputsDiff
platformDiff :: DerivationDiff -> Maybe (Changed Text)
builderDiff :: DerivationDiff -> Maybe (Changed Text)
argumentsDiff :: DerivationDiff -> Maybe ArgumentsDiff
sourcesDiff :: DerivationDiff -> SourcesDiff
inputsDiff :: DerivationDiff -> InputsDiff
envDiff :: DerivationDiff -> Maybe EnvironmentDiff
..} -> if
      | OutputsDiff Maybe (Changed (Map Text (DerivationOutput FilePath Text)))
Nothing [] <- OutputsDiff
outputsDiff
      , Maybe (Changed Text)
Nothing <- Maybe (Changed Text)
platformDiff
      , Maybe (Changed Text)
Nothing <- Maybe (Changed Text)
builderDiff
      , Maybe ArgumentsDiff
Nothing <- Maybe ArgumentsDiff
argumentsDiff
      , SourcesDiff Maybe (Changed (Set Text))
Nothing [] <- SourcesDiff
sourcesDiff
      , InputsDiff Maybe (Changed (Set Text))
Nothing [InputDerivationsDiff]
inputs <- InputsDiff
inputsDiff'
      , (InputDerivationsDiff -> Bool) -> [InputDerivationsDiff] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all InputDerivationsDiff -> Bool
alreadyComparedBelow [InputDerivationsDiff]
inputs
      , Maybe EnvironmentDiff -> Bool
envSkippedOrUnchanged Maybe EnvironmentDiff
envDiff
          -> Changed OutputStructure -> DerivationDiff
OnlyAlreadyComparedBelow Changed OutputStructure
outputStructure

      | Bool
otherwise -> DerivationDiff
          { Changed OutputStructure
outputStructure :: Changed OutputStructure
outputStructure :: Changed OutputStructure
outputStructure
          , OutputsDiff
outputsDiff :: OutputsDiff
outputsDiff :: OutputsDiff
outputsDiff
          , Maybe (Changed Text)
platformDiff :: Maybe (Changed Text)
platformDiff :: Maybe (Changed Text)
platformDiff
          , Maybe (Changed Text)
builderDiff :: Maybe (Changed Text)
builderDiff :: Maybe (Changed Text)
builderDiff
          , Maybe ArgumentsDiff
argumentsDiff :: Maybe ArgumentsDiff
argumentsDiff :: Maybe ArgumentsDiff
argumentsDiff
          , SourcesDiff
sourcesDiff :: SourcesDiff
sourcesDiff :: SourcesDiff
sourcesDiff
          , inputsDiff :: InputsDiff
inputsDiff = InputsDiff
inputsDiff'
          , Maybe EnvironmentDiff
envDiff :: Maybe EnvironmentDiff
envDiff :: Maybe EnvironmentDiff
envDiff
          }
    where
      inputsDiff' :: InputsDiff
inputsDiff' = (DerivationDiff -> DerivationDiff) -> InputsDiff -> InputsDiff
transformNestedDerivationDiffs
            DerivationDiff -> DerivationDiff
foldAlreadyComparedSubTrees
            InputsDiff
inputsDiff

{-| This transformation is most useful for
    --json output, because it will sqash a lot of
    `{"content":"  ","type":"Both"},{"content":"When","type":"Both"},{"content":" ","type":"Both"},{"content":"in","type":"Both"},{"content":" ","type":"Both"}`
    into one
    `{"content":"  When in ","type":"Both"}`
    block.

    To understand this problem clearer, see `golden-tests/expected-outputs/json`
    and `golden-tests/expected-outputs/json-squashed`.

    _Warning_: this transformation can break some parts of printing in
    human readable mode.
-}
squashSourcesAndEnvsDiff :: DerivationDiff -> DerivationDiff
squashSourcesAndEnvsDiff :: DerivationDiff -> DerivationDiff
squashSourcesAndEnvsDiff = (TextDiff -> TextDiff) -> DerivationDiff -> DerivationDiff
forall from to. Biplate from to => (to -> to) -> from -> from
transformBi
    \(TextDiff [Item Text]
x) -> [Item Text] -> TextDiff
TextDiff ([Item Text] -> [Item Text]
forall {a}. Semigroup a => [Item a] -> [Item a]
squashDiff [Item Text]
x)
  where
    squashDiff :: [Item a] -> [Item a]
squashDiff (Patience.Old a
a : Patience.Old a
b : [Item a]
xs) =
      [Item a] -> [Item a]
squashDiff (a -> Item a
forall a. a -> Item a
Patience.Old (a
a a -> a -> a
forall a. Semigroup a => a -> a -> a
<> a
b) Item a -> [Item a] -> [Item a]
forall a. a -> [a] -> [a]
: [Item a]
xs)
    squashDiff (Patience.New a
a : Patience.New a
b : [Item a]
xs) =
      [Item a] -> [Item a]
squashDiff (a -> Item a
forall a. a -> Item a
Patience.New (a
a a -> a -> a
forall a. Semigroup a => a -> a -> a
<> a
b) Item a -> [Item a] -> [Item a]
forall a. a -> [a] -> [a]
: [Item a]
xs)
    squashDiff (Patience.Both a
a a
_ : Patience.Both a
b a
_ : [Item a]
xs) =
      let ab :: a
ab = a
a a -> a -> a
forall a. Semigroup a => a -> a -> a
<> a
b in [Item a] -> [Item a]
squashDiff (a -> a -> Item a
forall a. a -> a -> Item a
Patience.Both a
ab a
ab Item a -> [Item a] -> [Item a]
forall a. a -> [a] -> [a]
: [Item a]
xs)
    squashDiff (Item a
x : [Item a]
xs) = Item a
x Item a -> [Item a] -> [Item a]
forall a. a -> [a] -> [a]
: [Item a] -> [Item a]
squashDiff [Item a]
xs
    squashDiff [] = []

-- ** Helpers

transformNestedDerivationDiffs
  :: (DerivationDiff -> DerivationDiff)
  -> InputsDiff
  -> InputsDiff
transformNestedDerivationDiffs :: (DerivationDiff -> DerivationDiff) -> InputsDiff -> InputsDiff
transformNestedDerivationDiffs DerivationDiff -> DerivationDiff
f InputsDiff{[InputDerivationsDiff]
Maybe (Changed (Set Text))
inputExtraNames :: Maybe (Changed (Set Text))
inputDerivationDiffs :: [InputDerivationsDiff]
inputExtraNames :: InputsDiff -> Maybe (Changed (Set Text))
inputDerivationDiffs :: InputsDiff -> [InputDerivationsDiff]
..} = InputsDiff
  { Maybe (Changed (Set Text))
inputExtraNames :: Maybe (Changed (Set Text))
inputExtraNames :: Maybe (Changed (Set Text))
inputExtraNames
  , inputDerivationDiffs :: [InputDerivationsDiff]
inputDerivationDiffs = (InputDerivationsDiff -> InputDerivationsDiff)
-> [InputDerivationsDiff] -> [InputDerivationsDiff]
forall a b. (a -> b) -> [a] -> [b]
map InputDerivationsDiff -> InputDerivationsDiff
changeDerivation [InputDerivationsDiff]
inputDerivationDiffs
  }
  where
    changeDerivation :: InputDerivationsDiff -> InputDerivationsDiff
changeDerivation InputDerivationsDiff
idd = case InputDerivationsDiff
idd of
      OneDerivationDiff Text
name DerivationDiff
dd ->
        Text -> DerivationDiff -> InputDerivationsDiff
OneDerivationDiff Text
name (DerivationDiff -> DerivationDiff
f DerivationDiff
dd)
      SomeDerivationsDiff {} -> InputDerivationsDiff
idd

envSkippedOrUnchanged :: Maybe EnvironmentDiff -> Bool
envSkippedOrUnchanged :: Maybe EnvironmentDiff -> Bool
envSkippedOrUnchanged = \case
  Maybe EnvironmentDiff
Nothing -> Bool
True
  Just EnvironmentDiff
EnvironmentsAreEqual -> Bool
True
  Maybe EnvironmentDiff
_ -> Bool
False

alreadyComparedBelow :: InputDerivationsDiff -> Bool
alreadyComparedBelow :: InputDerivationsDiff -> Bool
alreadyComparedBelow = \case
  OneDerivationDiff Text
_ DerivationDiff
AlreadyCompared -> Bool
True
  OneDerivationDiff Text
_ OnlyAlreadyComparedBelow{} -> Bool
True
  InputDerivationsDiff
_ -> Bool
False

transformIf :: Bool -> (DerivationDiff -> DerivationDiff) -> DerivationDiff -> DerivationDiff
transformIf :: Bool
-> (DerivationDiff -> DerivationDiff)
-> DerivationDiff
-> DerivationDiff
transformIf Bool
False DerivationDiff -> DerivationDiff
_ = DerivationDiff -> DerivationDiff
forall a. a -> a
id
transformIf Bool
True DerivationDiff -> DerivationDiff
f = DerivationDiff -> DerivationDiff
f