content: {-# 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
    deriving (Typeable, Eq, Ord, Enum, Generic, Hashable, Binary, NFData)
  
  instance Show VersionSortMethod where
    show = \case
      ParseVersion -> "parse_version"
      Vercmp -> "vercmp"
  
  instance Pretty VersionSortMethod where
    pretty ParseVersion = "ParseVersion"
    pretty Vercmp = "Vercmp"
  
  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
        ]
  