arch-hs-0.5.0.0: src/Distribution/ArchHs/Name.hs
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
-- | Copyright: (c) 2020 berberman
-- SPDX-License-Identifier: MIT
-- Maintainer: berberman <1793913507@qq.com>
-- Stability: experimental
-- Portability: portable
--
-- Naming conversion between haskell package in hackage and archlinux community repo.
--
-- To distribute a haskell package to archlinux, the name of package should be changed according to the naming convention:
--
-- (1) for haskell libraries, their names must have @haskell-@ prefix
--
-- (2) for programs, it depends on circumstances
--
-- (3) names should always be in lower case
--
-- However, it's not enough to prefix the string with @haskell-@ and trasform to lower case; in some special situations, the hackage name
-- may have @haskell-@ prefix already, or the case is irregular, thus we have to a name preset, @NAME_PRESET.json@, manually.
-- Once a package distributed to archlinux, whose name conform to above-mentioned situation, the name preset should be upgraded correspondingly.
--
-- @NAME_PRESET.json@ will be loaded during the compilation, generating haskell code to be called in runtime.
--
-- Converting a community name to hackage name following these steps:
--
-- (1) Find if the name preset contains this rule
-- (2) If it contains, then use it; or remove the @haskell-@ prefix
--
-- Converting a hackage name to community name following these steps:
--
-- (1) Find if the name preset contains this rule
-- (2) If it contains, then use it; or add the @haskell-@ prefix
--
-- For details, see the type 'MyName' and type class 'HasMyName' with its instances.
module Distribution.ArchHs.Name
( MyName,
unMyName,
HasMyName (..),
NameRep (..),
mToCommunityName,
mToHackageName,
toCommunityName,
toHackageName,
isHaskellPackage,
)
where
import Data.Char (toLower)
import Data.String (IsString, fromString)
import Distribution.ArchHs.Internal.NamePresetLoader
import Distribution.ArchHs.Internal.Prelude
import Distribution.ArchHs.Types
-- | The representation of a package name.
data NameRep
= -- | archlinx community style
CommunityRep
| -- | hackage style
HackageRep
$(loadNamePreset)
-- | Convert a name from community representation to hackage representation, according to the name preset.
-- If the preset doesn't contain this mapping rule, the function will return 'Nothing'.
-- This function is generated from @NAME_PRESET.json@
communityToHackageP :: MyName 'CommunityRep -> Maybe (MyName 'HackageRep)
-- | Convert a name from hackage representation to community representation, according to the name preset.
-- If the preset doesn't contain this mapping rule, the function will return 'Nothing'.
--
-- This function is generated from @NAME_PRESET.json@
hackageToCommunityP :: MyName 'HackageRep -> Maybe (MyName 'CommunityRep)
-- | Special haskell packages in community reop, which should be ignored in the process.
--
-- This function is generated from @NAME_PRESET.json@
falseListP :: [MyName 'CommunityRep]
-- | Community haskell packages of in the name preset.
--
-- This function is generated from @NAME_PRESET.json@
communityListP :: [MyName 'CommunityRep]
-- | A general package name representation.
-- It has a phantom @a@, which indexes this name.
-- Normally, the index should be the data kinds of 'NameRep'.
--
-- In Cabal API, packages' names are represented by the type 'PackageName';
-- in arch-hs, names parsed from @community.db@ are represented by the type 'CommunityName'.
-- It would be tedious to use two converting functions everywhere, so here comes a intermediate data type
-- to unify them, with type level constraints as bonus.
newtype MyName a = MyName
{ -- | Unwrap the value.
unMyName :: String
}
deriving stock (Show, Read, Eq, Ord, Generic)
deriving anyclass (NFData)
instance IsString (MyName a) where
fromString = MyName
-- | 'HasMyName' indicates that the type @a@ can be converted to 'MyName'.
-- This is where the actually conversion occurs.
class HasMyName a where
-- | To 'MyName' in hackage style.
toHackageRep :: a -> MyName 'HackageRep
-- | To 'MyName' in community style.
toCommunityRep :: a -> MyName 'CommunityRep
instance HasMyName (MyName 'CommunityRep) where
toHackageRep = toHackageRep . CommunityName . unMyName
toCommunityRep = id
instance HasMyName (MyName 'HackageRep) where
toHackageRep = id
toCommunityRep = toCommunityRep . mkPackageName . unMyName
instance HasMyName PackageName where
toHackageRep = MyName . unPackageName
toCommunityRep = go . unPackageName
where
go s = case hackageToCommunityP (MyName s) of
Just x -> x
_ ->
MyName . fmap toLower $
( if "haskell-" `isPrefixOf` s
then s
else "haskell-" <> s
)
instance HasMyName CommunityName where
toHackageRep = go . unCommunityName
where
go s = case communityToHackageP (MyName s) of
Just x -> x
_ -> MyName $ drop 8 s
toCommunityRep = MyName . unCommunityName
-- | Back to 'CommunityName'.
mToCommunityName :: MyName 'CommunityRep -> CommunityName
mToCommunityName = CommunityName . unMyName
-- | Back to 'PackageName'.
mToHackageName :: MyName 'HackageRep -> PackageName
mToHackageName = mkPackageName . unMyName
-- | Convert @n@ to 'CommunityName'.
toCommunityName :: HasMyName n => n -> CommunityName
toCommunityName = mToCommunityName . toCommunityRep
-- | Convert @n@ to 'PackageName'.
toHackageName :: HasMyName n => n -> PackageName
toHackageName = mToHackageName . toHackageRep
-- | Judge if a package in archlinux community repo is haskell package.
--
-- i.e. it is in @preset@ or have @haskell-@ prefix, and is not present in @falseList@ of @NAME_PRESET.json@.
isHaskellPackage :: CommunityName -> Bool
isHaskellPackage name =
let rep = toCommunityRep name
in (rep `elem` communityListP || "haskell-" `isPrefixOf` unMyName rep) && rep `notElem` falseListP