packages feed

nvfetcher-0.8.0.0: src/NvFetcher/Types.hs

{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingVia #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE UndecidableSuperClasses #-}

-- | Copyright: (c) 2021-2025 berberman
-- SPDX-License-Identifier: MIT
-- Maintainer: berberman <berberman@yandex.com>
-- Stability: experimental
-- Portability: portable
--
-- Types used in this program.
module NvFetcher.Types
  ( -- * Common types
    Version (..),
    Checksum (..),
    ContainerDigest (..),
    Branch (..),
    NixExpr,
    VersionChange (..),
    WithPackageKey (..),
    Glob (..),

    -- * Nvchecker types
    VersionSortMethod (..),
    ListOptions (..),
    VersionSource (..),
    NvcheckerResult (..),
    NvcheckerRaw (..),
    CheckVersion (..),
    NvcheckerOptions (..),
    UseStaleVersion (..),

    -- * Nix fetcher types
    RunFetch (..),
    ForceFetch (..),
    NixFetcher (..),
    FetchResult,
    FetchStatus (..),

    -- * ExtractSrc Types
    ExtractSrcQ (..),

    -- * FetchRustGitDeps types
    FetchRustGitDepsQ (..),

    -- * GetGitCommitDate types
    GitDateFormat (..),
    GetGitCommitDate (..),
    GitTimeZone (..),

    -- * Core types
    Core (..),

    -- * Package types
    PackageName,
    PackageFetcher,
    PackageExtractSrc (..),
    PackageCargoLockFiles (..),
    PackagePassthru (..),
    Package (..),
    PackageKey (..),
    PackageResult (..),
  )
where

import qualified Data.Aeson as A
import Data.Coerce (coerce)
import Data.Default
import Data.HashMap.Strict (HashMap)
import qualified Data.List.NonEmpty as NE
import Data.Maybe (fromMaybe, isNothing)
import Data.Proxy (Proxy (..))
import Data.String (IsString)
import Data.Text (Text)
import qualified Data.Text as T
import Development.Shake
import Development.Shake.Classes
import GHC.Generics (Generic)
import GHC.TypeLits (KnownSymbol, Symbol, symbolVal)
import Prettyprinter

--------------------------------------------------------------------------------

-- | Helper type for generating 'Show' and 'Pretty' instances
-- Type level string @d@ denotes the string used for the default value 'Nothing'
newtype DefaultableText (d :: Symbol) = DefaultableText (Maybe Text)

instance (KnownSymbol d) => Show (DefaultableText d) where
  show (DefaultableText Nothing) = "default (" <> symbolVal (Proxy :: Proxy d) <> ")"
  show (DefaultableText x) = show x

instance (KnownSymbol d) => Pretty (DefaultableText d) where
  pretty (DefaultableText Nothing) = pretty $ "default (" <> symbolVal (Proxy :: Proxy d) <> ")"
  pretty (DefaultableText (Just x)) = pretty x

-- | Package version
newtype Version = Version Text
  deriving newtype (Eq, Show, Ord, IsString, Semigroup, Monoid, A.FromJSON, A.ToJSON, Pretty)
  deriving stock (Typeable, Generic)
  deriving anyclass (Hashable, Binary, NFData)

-- | Check sum, sha256, sri or base32, etc.
newtype Checksum = Checksum Text
  deriving newtype (Show, Eq, Ord, A.FromJSON, A.ToJSON, Pretty)
  deriving stock (Typeable, Generic)
  deriving anyclass (Hashable, Binary, NFData)

-- | Digest of a (Docker) container
newtype ContainerDigest = ContainerDigest Text
  deriving newtype (Show, Eq, Ord, A.FromJSON, A.ToJSON, Pretty)
  deriving stock (Typeable, Generic)
  deriving anyclass (Hashable, Binary, NFData)

-- | Git branch ('Nothing': master)
newtype Branch = Branch (Maybe Text)
  deriving newtype (Eq, Ord, Default)
  deriving stock (Typeable, Generic)
  deriving anyclass (Hashable, Binary, NFData)
  deriving (Pretty, Show) via DefaultableText "master"

-- | Version change of a package
--
-- >>> VersionChange "foo" Nothing "2.3.3"
-- foo: ∅ → 2.3.3
--
-- >>> VersionChange "bar" (Just "2.2.2") "2.3.3"
-- bar: 2.2.2 → 2.3.3
data VersionChange = VersionChange
  { vcName :: PackageName,
    vcOld :: Maybe Version,
    vcNew :: Version
  }
  deriving (Eq)

instance Show VersionChange where
  show VersionChange {..} =
    T.unpack $ vcName <> ": " <> fromMaybe "∅" (coerce vcOld) <> " → " <> coerce vcNew

-- | Nix expression
type NixExpr = Text

--------------------------------------------------------------------------------

data VersionSortMethod = ParseVersion | Vercmp | AwesomeVersion
  deriving (Typeable, Eq, Ord, Enum, Generic, Hashable, Binary, NFData)

instance Show VersionSortMethod where
  show = \case
    ParseVersion -> "parse_version"
    Vercmp -> "vercmp"
    AwesomeVersion -> "awesomeversion"

instance Pretty VersionSortMethod where
  pretty ParseVersion = "ParseVersion"
  pretty Vercmp = "Vercmp"
  pretty AwesomeVersion = "AwesomeVersion"

instance Default VersionSortMethod where
  def = ParseVersion

-- | Filter-like configuration for some version sources.
-- See <https://nvchecker.readthedocs.io/en/latest/usage.html#list-options> for details.
data ListOptions = ListOptions
  { _includeRegex :: Maybe Text,
    _excludeRegex :: Maybe Text,
    _sortVersionKey :: Maybe VersionSortMethod,
    _ignored :: Maybe Text
  }
  deriving (Show, Typeable, Eq, Ord, Generic, Hashable, Binary, NFData, Default)

isEmptyListOptions :: ListOptions -> Bool
isEmptyListOptions ListOptions {..} =
  isNothing _includeRegex
    && isNothing _excludeRegex
    && isNothing _sortVersionKey
    && isNothing _includeRegex

instance Pretty ListOptions where
  pretty ListOptions {..} =
    "ListOptions"
      <> indent
        2
        ( vsep $
            concat
              [ ppField "includeRegex" _includeRegex,
                ppField "excludeRegex" _excludeRegex,
                ppField "sortVersionKey" _sortVersionKey,
                ppField "ignored" _includeRegex
              ]
        )

-- | Configuration available for evey version sourece.
-- See <https://nvchecker.readthedocs.io/en/latest/usage.html#global-options> for details.
data NvcheckerOptions = NvcheckerOptions
  { _stripPrefix :: Maybe Text,
    _fromPattern :: Maybe Text,
    _toPattern :: Maybe Text
  }
  deriving (Show, Typeable, Eq, Ord, Generic, Hashable, Binary, NFData, Default)

isEmptyNvcheckerOptions :: NvcheckerOptions -> Bool
isEmptyNvcheckerOptions NvcheckerOptions {..} =
  isNothing _stripPrefix
    && isNothing _fromPattern
    && isNothing _toPattern

instance Pretty NvcheckerOptions where
  pretty NvcheckerOptions {..} =
    "NvcheckerOptions"
      <> line
      <> indent
        2
        ( vsep $
            concat
              [ ppField "stripPrefix" _stripPrefix,
                ppField "fromPattern" _fromPattern,
                ppField "toPattern" _toPattern
              ]
        )

ppField :: (Pretty a) => Doc ann -> Maybe a -> [Doc ann]
ppField _ Nothing = []
ppField s (Just x) = [s <> colon <+> pretty x]

-- | Upstream version source for nvchecker to check
data VersionSource
  = GitHubRelease {_owner :: Text, _repo :: Text}
  | GitHubTag {_owner :: Text, _repo :: Text, _listOptions :: ListOptions}
  | Git {_vurl :: Text, _vbranch :: Branch}
  | Pypi {_pypi :: Text}
  | ArchLinux {_archpkg :: Text}
  | Aur {_aur :: Text}
  | Manual {_manual :: Text}
  | Repology {_repology :: Text, _repo :: Text}
  | Webpage {_vurl :: Text, _regex :: Text, _listOptions :: ListOptions}
  | HttpHeader {_vurl :: Text, _regex :: Text, _listOptions :: ListOptions}
  | OpenVsx {_ovPublisher :: Text, _ovExtName :: Text}
  | VscodeMarketplace {_vsmPublisher :: Text, _vsmExtName :: Text}
  | Cmd {_vcmd :: Text}
  | Container {_vcontainer :: Text, _listOptions :: ListOptions}
  deriving (Show, Typeable, Eq, Ord, Generic, Hashable, Binary, NFData)

instance Pretty VersionSource where
  pretty GitHubRelease {..} =
    "CheckGitHubRelease"
      <> line
      <> indent
        2
        ( vsep
            [ "owner" <> colon <+> pretty _owner,
              "repo" <> colon <+> pretty _repo
            ]
        )
  pretty GitHubTag {..} =
    "CheckGitHubTag"
      <> line
      <> indent
        2
        ( vsep $
            [ "owner" <> colon <+> pretty _owner,
              "repo" <> colon <+> pretty _repo
            ]
              <> ["listOptions" <> colon <+> pretty _listOptions | not $ isEmptyListOptions _listOptions]
        )
  pretty Git {..} =
    "CheckGit"
      <> line
      <> indent
        2
        ( vsep
            [ "url" <> colon <+> pretty _vurl,
              "branch" <> colon <+> pretty _vbranch
            ]
        )
  pretty Pypi {..} =
    "CheckPypi" <> colon <+> pretty _pypi
  pretty ArchLinux {..} =
    "CheckArchLinux" <> colon <+> pretty _archpkg
  pretty Aur {..} =
    "CheckAur" <> colon <+> pretty _aur
  pretty Manual {..} =
    "CheckManual" <> colon <+> pretty _manual
  pretty Repology {..} =
    "CheckRepology"
      <> line
      <> indent
        2
        ( vsep
            [ "repology" <> colon <+> pretty _repology,
              "repo" <> colon <+> pretty _repo
            ]
        )
  pretty Webpage {..} =
    "CheckWebpage"
      <> line
      <> indent
        2
        ( vsep $
            [ "url" <> colon <+> pretty _vurl,
              "regex" <> colon <+> pretty _regex
            ]
              <> ["listOptions" <> colon <+> pretty _listOptions | not $ isEmptyListOptions _listOptions]
        )
  pretty HttpHeader {..} =
    "CheckHttpHeader"
      <> line
      <> indent
        2
        ( vsep $
            [ "url" <> colon <+> pretty _vurl,
              "regex" <> colon <+> pretty _regex
            ]
              <> ["listOptions" <> colon <+> pretty _listOptions | not $ isEmptyListOptions _listOptions]
        )
  pretty OpenVsx {..} =
    "CheckOpenVsx"
      <> line
      <> indent
        2
        ( vsep
            [ "publisher" <> colon <+> pretty _ovPublisher,
              "extName" <> colon <+> pretty _ovExtName
            ]
        )
  pretty VscodeMarketplace {..} =
    "CheckVscodeMarketplace"
      <> line
      <> indent
        2
        ( vsep
            [ "publisher" <> colon <+> pretty _vsmPublisher,
              "extName" <> colon <+> pretty _vsmExtName
            ]
        )
  pretty Cmd {..} =
    "CheckCmd" <> colon <+> pretty _vcmd
  pretty Container {..} =
    "CheckContainer" <> colon <+> pretty _vcontainer

-- | The input of nvchecker
data CheckVersion = CheckVersion VersionSource NvcheckerOptions
  deriving (Show, Typeable, Eq, Ord, Generic, Hashable, Binary, NFData)

instance Pretty CheckVersion where
  pretty (CheckVersion v n) = align (vsep $ [pretty v] <> [pretty n | not $ isEmptyNvcheckerOptions n])

-- | The result of nvchecker rule
data NvcheckerResult = NvcheckerResult
  { nvNow :: Version,
    -- | last result of this nvchecker rule
    -- TODO: consider removing this field
    nvOld :: Maybe Version,
    -- | stale means even 'nvNow' comes from json file (last run)
    -- and we actually didn't run nvchecker this time. 'nvOld' will be 'Nothing' in this case.
    nvStale :: Bool
  }
  deriving (Show, Typeable, Eq, Generic, Hashable, Binary, NFData)

-- | Parsed JSON output from nvchecker
data NvcheckerRaw = NvcheckerSuccess Version | NvcheckerError Text
  deriving (Show, Typeable, Eq, Generic)

instance A.FromJSON NvcheckerRaw where
  parseJSON = A.withObject "NvcheckerRaw" $ \o -> do
    mVersion <- o A..:? "version"
    case mVersion of
      Just version -> pure $ NvcheckerSuccess version
      _ -> NvcheckerError <$> o A..: "error"

type instance RuleResult CheckVersion = NvcheckerResult

--------------------------------------------------------------------------------

-- | Whether to cache the fetched sha256
--
-- @ForceFetch@ indicates @alwaysRerun@ the fetcher rule
data ForceFetch = ForceFetch | NoForceFetch
  deriving (Show, Eq, Ord, Hashable, NFData, Binary, Typeable, Generic)

instance Pretty ForceFetch where
  pretty ForceFetch = "ForceFetch"
  pretty NoForceFetch = "NoForceFetch"

instance Default ForceFetch where
  def = NoForceFetch

-- | The input of prefetch rule
data RunFetch = RunFetch ForceFetch (NixFetcher Fresh)
  deriving (Show, Eq, Ord, Hashable, NFData, Binary, Typeable, Generic)

-- Prefetch rule never throws exceptions
type instance RuleResult RunFetch = Maybe (NixFetcher Fetched)

-- | If the package is prefetched, then we can obtain the SHA256
data NixFetcher (k :: FetchStatus)
  = FetchGit
      { _furl :: Text,
        _rev :: Version,
        _deepClone :: Bool,
        _fetchSubmodules :: Bool,
        _leaveDotGit :: Bool,
        _sparseCheckout :: [Text],
        _name :: Maybe Text,
        _sha256 :: FetchResult Checksum k
      }
  | FetchGitHub
      { _fowner :: Text,
        _frepo :: Text,
        _rev :: Version,
        _deepClone :: Bool,
        _fetchSubmodules :: Bool,
        _leaveDotGit :: Bool,
        _sparseCheckout :: [Text],
        _name :: Maybe Text,
        _sha256 :: FetchResult Checksum k
      }
  | FetchUrl
      { _furl :: Text,
        _name :: Maybe Text,
        _sha256 :: FetchResult Checksum k
      }
  | FetchTarball
      { _furl :: Text,
        _sha256 :: FetchResult Checksum k
      }
  | FetchDocker
      { _imageName :: Text,
        _imageTag :: Text,
        _imageDigest :: FetchResult ContainerDigest k,
        _sha256 :: FetchResult Checksum k,
        _fos :: Maybe Text,
        _farch :: Maybe Text,
        _finalImageName :: Maybe Text,
        _finalImageTag :: Maybe Text,
        _tlsVerify :: Maybe Bool
      }
  deriving (Typeable, Generic)

class (c (FetchResult Checksum k), c (FetchResult ContainerDigest k)) => ForFetchResult c k

instance (c (FetchResult Checksum k), c (FetchResult ContainerDigest k)) => ForFetchResult c k

deriving instance (Show `ForFetchResult` k) => Show (NixFetcher k)

deriving instance (Eq `ForFetchResult` k) => Eq (NixFetcher k)

deriving instance (Ord `ForFetchResult` k) => Ord (NixFetcher k)

deriving instance (Hashable `ForFetchResult` k) => Hashable (NixFetcher k)

deriving instance (Binary `ForFetchResult` k) => Binary (NixFetcher k)

deriving instance (NFData `ForFetchResult` k) => NFData (NixFetcher k)

-- | Fetch status
data FetchStatus = Fresh | Fetched

-- | Prefetched fetchers hold hashes
type family FetchResult a (k :: FetchStatus) where
  FetchResult _ Fresh = ()
  FetchResult a Fetched = a

instance A.ToJSON (NixFetcher Fetched) where
  toJSON FetchGit {..} =
    A.object
      [ "url" A..= _furl,
        "rev" A..= _rev,
        "deepClone" A..= _deepClone,
        "fetchSubmodules" A..= _fetchSubmodules,
        "leaveDotGit" A..= _leaveDotGit,
        "sparseCheckout" A..= _sparseCheckout,
        "name" A..= _name,
        "sha256" A..= _sha256,
        "type" A..= A.String "git"
      ]
  toJSON FetchGitHub {..} =
    A.object
      [ "owner" A..= _fowner,
        "repo" A..= _frepo,
        "rev" A..= _rev,
        "deepClone" A..= _deepClone,
        "fetchSubmodules" A..= _fetchSubmodules,
        "leaveDotGit" A..= _leaveDotGit,
        "sparseCheckout" A..= _sparseCheckout,
        "name" A..= _name,
        "sha256" A..= _sha256,
        "type" A..= A.String "github"
      ]
  toJSON FetchUrl {..} =
    A.object
      [ "url" A..= _furl,
        "name" A..= _name,
        "sha256" A..= _sha256,
        "type" A..= A.String "url"
      ]
  toJSON FetchTarball {..} =
    A.object
      [ "url" A..= _furl,
        "sha256" A..= _sha256,
        "type" A..= A.String "tarball"
      ]
  toJSON FetchDocker {..} =
    A.object
      [ "imageName" A..= _imageName,
        "imageTag" A..= _imageTag,
        "imageDigest" A..= _imageDigest,
        "sha256" A..= _sha256,
        "os" A..= _fos,
        "arch" A..= _farch,
        "finalImageName" A..= _finalImageName,
        "finalImageTag" A..= _finalImageTag,
        "tlsVerify" A..= _tlsVerify
      ]

instance Pretty (NixFetcher k) where
  pretty FetchGit {..} =
    "FetchGit"
      <> line
      <> indent
        2
        ( vsep $
            [ "url" <> colon <+> pretty _furl,
              "rev" <> colon <+> pretty _rev,
              "deepClone" <> colon <+> pretty _deepClone,
              "fetchSubmodules" <> colon <+> pretty _fetchSubmodules,
              "leaveDotGit" <> colon <+> pretty _leaveDotGit,
              "sparseCheckout" <> colon <+> pretty _sparseCheckout
            ]
              <> ppField "name" _name
        )
  pretty FetchGitHub {..} =
    "FetchGitHub"
      <> line
      <> indent
        2
        ( vsep $
            [ "owner" <> colon <+> pretty _fowner,
              "repo" <> colon <+> pretty _frepo,
              "rev" <> colon <+> pretty _rev,
              "deepClone" <> colon <+> pretty _deepClone,
              "fetchSubmodules" <> colon <+> pretty _fetchSubmodules,
              "leaveDotGit" <> colon <+> pretty _leaveDotGit,
              "sparseCheckout" <> colon <+> pretty _sparseCheckout
            ]
              <> ppField "name" _name
        )
  pretty FetchUrl {..} =
    "FetchUrl"
      <> line
      <> indent
        2
        ( vsep $
            ["url" <> colon <+> pretty _furl]
              <> ppField "name" _name
        )
  pretty FetchTarball {..} =
    "FetchTarball" <> colon <+> pretty _furl
  pretty FetchDocker {..} =
    "FetchDocker"
      <> line
      <> indent
        2
        ( vsep $
            [ "imageName" <> colon <+> pretty _imageName,
              "imageTag" <> colon <+> pretty _finalImageTag
            ]
              <> ppField "os" _fos
              <> ppField "arch" _farch
              <> ppField "finalImageName" _finalImageName
              <> ppField "finalImageTag" _finalImageTag
              <> ppField "tlsVerify" _tlsVerify
        )

--------------------------------------------------------------------------------

-- | Zsh style glob pattern
-- Notably, recursive wildcards like @**/@ are supported.
newtype Glob = Glob FilePath
  deriving newtype (Show, Eq, Ord, IsString, Pretty)
  deriving stock (Typeable, Generic)
  deriving anyclass (Hashable, Binary, NFData)

-- | Extract file contents from package source
-- Matched files will be copied to build dir.
-- All matched directories are ignored. Only files will be processed.
data ExtractSrcQ = ExtractSrcQ (NixFetcher Fetched) (NE.NonEmpty Glob)
  deriving (Show, Eq, Ord, Hashable, NFData, Binary, Typeable, Generic)

-- | Rule result for extracting source files. 'Text' is the file contents,
-- only available if the second element of the tuple in 'ExtractSrcQ' is @True@.
-- The key of the result map is the file path relative to the package source,
-- and the value is the file path relative to the build directory.
type instance RuleResult ExtractSrcQ = HashMap FilePath FilePath

instance Pretty ExtractSrcQ where
  pretty (ExtractSrcQ f n) =
    "ExtractSrc"
      <> line
      <> indent
        2
        ( vsep
            [ "fetcher" <> colon <+> pretty f,
              "files" <> colon <+> pretty n
            ]
        )

--------------------------------------------------------------------------------

-- | Fetch @outputHashes@ for git dependencies in @Cargo.lock@.
-- See <https://github.com/NixOS/nixpkgs/blob/master/doc/languages-frameworks/rust.section.md#importing-a-cargolock-file> for details.
-- We need fetched source and the file path to @Cargo.lock@.
data FetchRustGitDepsQ = FetchRustGitDepsQ (NixFetcher Fetched) FilePath
  deriving (Show, Eq, Ord, Hashable, NFData, Binary, Typeable, Generic)

-- | @outputHashes@, a mapping from nameVer -> output hash
type instance RuleResult FetchRustGitDepsQ = HashMap Text Checksum

instance Pretty FetchRustGitDepsQ where
  pretty (FetchRustGitDepsQ f n) =
    "FetchRustGitDeps"
      <> line
      <> indent
        2
        ( vsep
            [ "fetcher" <> colon <+> pretty f,
              "cargoLock" <> colon <+> pretty n
            ]
        )

--------------------------------------------------------------------------------

-- | @strftime@ format
-- Defaults to @%Y-%m-%d@
newtype GitDateFormat = GitDateFormat (Maybe Text)
  deriving newtype (Eq, Ord, Default)
  deriving stock (Typeable, Generic)
  deriving anyclass (Hashable, Binary, NFData)
  deriving (Pretty, Show) via DefaultableText "%Y-%m-%d"

-- | Defaults to commit's time zone.
-- When set to @local@, current local time zone is used.
-- Only used in 'GetGitCommitDate'.
newtype GitTimeZone = GitTimeZone (Maybe Text)
  deriving newtype (Eq, Ord, Default)
  deriving stock (Typeable, Generic)
  deriving anyclass (Hashable, Binary, NFData)
  deriving (Pretty, Show) via DefaultableText "commit's time zone"

-- | Get the commit date by using shallow clone
--
-- @_gformat@ is in.
-- Note: Requires git >= 2.5
data GetGitCommitDate = GetGitCommitDate
  { _gurl :: Text,
    _grev :: Text,
    _gformat :: (GitDateFormat, GitTimeZone)
  }
  deriving (Show, Eq, Ord, Hashable, NFData, Binary, Typeable, Generic)

type instance RuleResult GetGitCommitDate = Text

instance Pretty GetGitCommitDate where
  pretty GetGitCommitDate {..} =
    "GetGitCommitDate"
      <> line
      <> indent
        2
        ( vsep
            [ "url" <> colon <+> pretty _gurl,
              "rev" <> colon <+> pretty _grev,
              "format" <> colon <+> pretty _gformat
            ]
        )

--------------------------------------------------------------------------------

-- | Package name, used in generating nix expr
type PackageName = Text

-- | How to create package source fetcher given a version
type PackageFetcher = Version -> NixFetcher Fresh

newtype PackageExtractSrc = PackageExtractSrc (NE.NonEmpty Glob)

newtype PackageCargoLockFiles = PackageCargoLockFiles (NE.NonEmpty Glob)

newtype PackagePassthru = PackagePassthru (HashMap Text Text)
  deriving newtype (Semigroup, Monoid)

-- | Using stale value indicates that we will /NOT/ check for new versions if
-- there is a known version recovered from json file or last use of the rule.
-- Normally you don't want a stale version
-- unless you need pin a package.
data UseStaleVersion
  = -- | Specified in configuration file
    PermanentStale
  | -- | Specified by @--filter@ command
    TemporaryStale
  | NoStale
  deriving stock (Eq, Show, Ord, Typeable, Generic)
  deriving anyclass (Hashable, Binary, NFData)

-- | A package is defined with:
--
-- 1. its name
-- 2. how to track its version
-- 3. how to fetch it as we have the version
-- 4. optional file paths to extract (dump to build dir)
-- 5. optional @Cargo.lock@ paths (if it's a rust package)
-- 6. an optional pass through map
-- 7. if the package version was pinned
-- 8. optional git date format with time zone (if the version source is git)
-- 9. whether to always fetch a package regardless of the version changing
-- /INVARIANT: 'Version' passed to 'PackageFetcher' MUST be used textually,/
-- /i.e. can only be concatenated with other strings,/
-- /in case we can't check the equality between fetcher functions./
data Package = Package
  { _pname :: PackageName,
    _pversion :: CheckVersion,
    _pfetcher :: PackageFetcher,
    _pextract :: Maybe PackageExtractSrc,
    _pcargo :: Maybe PackageCargoLockFiles,
    _ppassthru :: PackagePassthru,
    _ppinned :: UseStaleVersion,
    _pgitdate :: (GitDateFormat, GitTimeZone),
    _pforcefetch :: ForceFetch
  }

-- | Package key is the name of a package.
-- We use this type to index packages.
newtype PackageKey = PackageKey PackageName
  deriving newtype (Eq, Show, Ord, Pretty)
  deriving stock (Typeable, Generic)
  deriving anyclass (Hashable, Binary, NFData)

--------------------------------------------------------------------------------

-- | The key type of nvfetcher rule. See "NvFetcher.Core"
data Core = Core
  deriving (Eq, Show, Ord, Typeable, Generic, Hashable, Binary, NFData)

-- If prefetch fails, we don't want to fail the whole build
type instance RuleResult Core = Maybe PackageResult

-- | Decorate a rule's key with 'PackageKey'
newtype WithPackageKey k = WithPackageKey (k, PackageKey)
  deriving newtype (Eq, Hashable, Binary, NFData)

instance (Show k) => Show (WithPackageKey k) where
  show (WithPackageKey (k, n)) = show k <> " (" <> show n <> ")"

type instance RuleResult (WithPackageKey k) = RuleResult k

-- | Result type of 'Core'
data PackageResult = PackageResult
  { _prname :: PackageName,
    _prversion :: NvcheckerResult,
    _prfetched :: NixFetcher 'Fetched,
    _prpassthru :: Maybe (HashMap Text Text),
    -- | file path relative to package source -> file path relative to build dir
    _prextract :: Maybe (HashMap FilePath FilePath),
    -- | cargo lock file path relative to package source -> (lock file path relative to build dir, git dependencies)
    _prcargolock :: Maybe (HashMap FilePath (FilePath, HashMap Text Checksum)),
    _prpinned :: UseStaleVersion,
    _prgitdate :: Maybe Text
  }
  deriving (Show, Typeable, Generic, NFData)

instance A.ToJSON PackageResult where
  toJSON PackageResult {..} =
    A.object
      [ "name" A..= _prname,
        "version" A..= nvNow _prversion,
        "src" A..= _prfetched,
        "extract" A..= _prextract,
        "passthru" A..= _prpassthru,
        "cargoLock" A..= _prcargolock,
        "pinned" A..= case _prpinned of
          PermanentStale -> True
          _ -> False,
        "date" A..= _prgitdate
      ]