packages feed

henforcer-1.0.0.0: src/Henforcer/CodeStructure/Import/Scheme/Scheme.hs

{- |
Module      : Henforcer.CodeStructure.Import.Scheme.Scheme
Description : Models the style of import including qualification, alias, and safety.
Copyright   : (c) Flipstone Technology Partners, 2023-2026
License     : MIT
Maintainer  : maintainers@flipstone.com
-}
module Henforcer.CodeStructure.Import.Scheme.Scheme
  ( Scheme (..)
  , SchemeWithNote (..)
  , AllowedSchemes
  , buildScheme
  , allowedSchemesCodec
  , keepOnlyPackageNameInQualifier
  ) where

import qualified Control.Monad as M
import qualified Data.Map.Strict as Map
import qualified Data.String as String
import qualified Data.Text as T
import qualified Toml

import qualified CompatGHC
import Henforcer.CodeStructure.Import.Scheme.Alias
  ( Alias
  , aliasCodecWithDefault
  , determineAlias
  )
import Henforcer.CodeStructure.Import.Scheme.PackageQualifier
  ( PackageQualifier (WithPackageQualifier, WithoutPackageQualifier)
  , determinePackageQualifier
  , keepPackageNameOnly
  , packageQualifierCodec
  )
import Henforcer.CodeStructure.Import.Scheme.Safe (Safe, determineSafe, safeCodec)
import qualified Henforcer.Rules as Rules
import qualified TomlHelper

{- | Representation of the structure of on an import, covering the qualification, any aliasing, and
the safety.

@since 1.0.0.0
-}
data Scheme = Scheme
  { qualification :: !CompatGHC.ImportDeclQualifiedStyle
  , alias :: !Alias
  , safe :: !Safe
  , packageQualification :: !PackageQualifier
  }
  deriving (Eq, Show)

{- | The 'Scheme' along with the 'UserNote' that was requested to be printed in the case of failures.

@since 1.0.0.0
-}
data SchemeWithNote = SchemeWithNote
  { schemeNote :: !Rules.UserNote
  , underlyingScheme :: !Scheme
  }
  deriving (Eq, Show)

{- | The 'SchemeWithNote's that should be allowed for each 'CompatGHC.ModuleName'.

@since 1.0.0.0
-}
type AllowedSchemes =
  Map.Map CompatGHC.ModuleName [SchemeWithNote]

{- | Conversion for 'AllowedSchemes' that performs conversion through 'SchemeToml'.

@since 1.0.0.0
-}
allowedSchemesCodec :: Toml.Key -> Toml.TomlCodec AllowedSchemes
allowedSchemesCodec =
  Toml.dimap ((fmap . fmap) schemeToToml) (fmap tomlsToSchemes)
    . Toml.map
      (CompatGHC.moduleNameCodec (String.fromString "module"))
      (Toml.list schemeTomlCodec (String.fromString "importScheme"))

{- | Compute the 'Scheme' from an import

@since 1.0.0.0
-}
buildScheme :: CompatGHC.ImportDecl CompatGHC.GhcRn -> Scheme
buildScheme imp =
  Scheme
    { qualification = CompatGHC.ideclQualified imp
    , alias = determineAlias imp
    , safe = determineSafe imp
    , packageQualification = determinePackageQualifier imp
    }

{- | The Toml verion of an import scheme. This is an intermediate type to facilitate configuration in
   a more ergonomic manner.

@since 1.0.0.0
-}
data SchemeToml = SchemeToml
  { tomlAlias :: !Alias
  , tomlSafe :: !Safe
  , tomlPackageQualification :: !PackageQualifier
  , tomlQualification :: !QualificationToml
  , tomlSchemeNote :: !Rules.UserNote
  }

schemeTomlCodec :: Toml.TomlCodec SchemeToml
schemeTomlCodec =
  SchemeToml
    <$> TomlHelper.addField "alias" tomlAlias aliasCodecWithDefault
    <*> TomlHelper.addField "safe" tomlSafe safeCodec
    <*> TomlHelper.addField
      "packageQualified"
      tomlPackageQualification
      (TomlHelper.setDefault WithoutPackageQualifier packageQualifierCodec)
    <*> TomlHelper.addField "qualified" tomlQualification (Toml.table qualificationTomlCodec)
    <*> Rules.userNoteField tomlSchemeNote

schemeToToml :: SchemeWithNote -> SchemeToml
schemeToToml withNote =
  let
    s = underlyingScheme withNote
   in
    SchemeToml
      { tomlAlias = alias s
      , tomlSafe = safe s
      , tomlPackageQualification = packageQualification s
      , tomlQualification = qualificationStyleToToml $ qualification s
      , tomlSchemeNote = schemeNote withNote
      }

tomlsToSchemes :: [SchemeToml] -> [SchemeWithNote]
tomlsToSchemes =
  M.join . fmap explodeSchemeToml

explodeSchemeToml :: SchemeToml -> [SchemeWithNote]
explodeSchemeToml st =
  explodeQualificationToml
    (tomlSchemeNote st)
    (tomlAlias st)
    (tomlSafe st)
    (tomlPackageQualification st)
    (tomlQualification st)

{- | Filters out version and seperators so we can look only at the package portion for comparsion.

@since 1.0.0.0
-}
keepOnlyPackageNameInQualifier :: Scheme -> Scheme
keepOnlyPackageNameInQualifier s =
  let
    mbGetQualifierText :: PackageQualifier -> Maybe T.Text
    mbGetQualifierText (WithPackageQualifier t) = Just t
    mbGetQualifierText WithoutPackageQualifier = Nothing

    newQualifier =
      maybe WithoutPackageQualifier WithPackageQualifier
        . M.join
        . fmap keepPackageNameOnly
        . mbGetQualifierText
        $ packageQualification s
   in
    s{packageQualification = newQualifier}

{- | Representation of the choices for qualification as it is presented in the toml config.

@since 1.0.0.0
-}
data QualificationToml = QualificationToml
  { qualifiedPre :: !Bool
  , qualifiedPost :: !Bool
  , unqualified :: !Bool
  }

qualificationTomlCodec :: Toml.TomlCodec QualificationToml
qualificationTomlCodec =
  QualificationToml
    <$> TomlHelper.addField "qualifiedPre" qualifiedPre (TomlHelper.setDefault False Toml.bool)
    <*> TomlHelper.addField "qualifiedPost" qualifiedPost (TomlHelper.setDefault False Toml.bool)
    <*> TomlHelper.addField "unqualified" unqualified (TomlHelper.setDefault False Toml.bool)

qualificationTomlToQualifications :: QualificationToml -> [CompatGHC.ImportDeclQualifiedStyle]
qualificationTomlToQualifications i =
  case i of
    (QualificationToml False False False) -> []
    (QualificationToml True False False) -> [CompatGHC.QualifiedPre]
    (QualificationToml False True False) -> [CompatGHC.QualifiedPost]
    (QualificationToml False False True) -> [CompatGHC.NotQualified]
    (QualificationToml True True False) -> [CompatGHC.QualifiedPre, CompatGHC.QualifiedPost]
    (QualificationToml True False True) -> [CompatGHC.QualifiedPre, CompatGHC.NotQualified]
    (QualificationToml False True True) -> [CompatGHC.QualifiedPost, CompatGHC.NotQualified]
    (QualificationToml True True True) -> [CompatGHC.QualifiedPre, CompatGHC.QualifiedPost, CompatGHC.NotQualified]

qualificationStyleToToml :: CompatGHC.ImportDeclQualifiedStyle -> QualificationToml
qualificationStyleToToml CompatGHC.QualifiedPre = QualificationToml True False False
qualificationStyleToToml CompatGHC.QualifiedPost = QualificationToml False True False
qualificationStyleToToml CompatGHC.NotQualified = QualificationToml False False True

explodeQualificationToml ::
  Rules.UserNote -> Alias -> Safe -> PackageQualifier -> QualificationToml -> [SchemeWithNote]
explodeQualificationToml note a s p =
  let
    mkScheme q =
      SchemeWithNote note (Scheme q a s p)
   in
    fmap mkScheme . qualificationTomlToQualifications