{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}

-- | Copyright: (c) 2020-2021 berberman
-- SPDX-License-Identifier: MIT
-- Maintainer: berberman <berberman@yandex.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 archlinux 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 archlinux 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,
    NameRep (..),
    HasMyName (..),
    toArchLinuxName,
    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
    ArchLinuxRep
  | -- | hackage style
    HackageRep

$([MyName 'ArchLinuxRep]
MyName 'ArchLinuxRep -> Maybe (MyName 'HackageRep)
MyName 'HackageRep -> Maybe (MyName 'ArchLinuxRep)
communityListP :: [MyName 'ArchLinuxRep]
falseListP :: [MyName 'ArchLinuxRep]
hackageToCommunityP :: MyName 'HackageRep -> Maybe (MyName 'ArchLinuxRep)
communityToHackageP :: MyName 'ArchLinuxRep -> Maybe (MyName '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 'ArchLinuxRep -> Maybe (MyName 'HackageRep)

-- | Convert a name from hackage representation to archlinux 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 'ArchLinuxRep)

-- | Special haskell packages in community repo, which should be ignored in the process.
--
-- This function is generated from @NAME_PRESET.json@
falseListP :: [MyName 'ArchLinuxRep]

-- | Community haskell packages of in the name preset.
--
-- This function is generated from @NAME_PRESET.json@
communityListP :: [MyName 'ArchLinuxRep]

-- | 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 'ArchLinuxName'.
-- 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.
    MyName a -> String
unsafeUnMyName :: String
  }
  deriving stock (Int -> MyName a -> ShowS
[MyName a] -> ShowS
MyName a -> String
(Int -> MyName a -> ShowS)
-> (MyName a -> String) -> ([MyName a] -> ShowS) -> Show (MyName a)
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
forall k (a :: k). Int -> MyName a -> ShowS
forall k (a :: k). [MyName a] -> ShowS
forall k (a :: k). MyName a -> String
showList :: [MyName a] -> ShowS
$cshowList :: forall k (a :: k). [MyName a] -> ShowS
show :: MyName a -> String
$cshow :: forall k (a :: k). MyName a -> String
showsPrec :: Int -> MyName a -> ShowS
$cshowsPrec :: forall k (a :: k). Int -> MyName a -> ShowS
Show, ReadPrec [MyName a]
ReadPrec (MyName a)
Int -> ReadS (MyName a)
ReadS [MyName a]
(Int -> ReadS (MyName a))
-> ReadS [MyName a]
-> ReadPrec (MyName a)
-> ReadPrec [MyName a]
-> Read (MyName a)
forall a.
(Int -> ReadS a)
-> ReadS [a] -> ReadPrec a -> ReadPrec [a] -> Read a
forall k (a :: k). ReadPrec [MyName a]
forall k (a :: k). ReadPrec (MyName a)
forall k (a :: k). Int -> ReadS (MyName a)
forall k (a :: k). ReadS [MyName a]
readListPrec :: ReadPrec [MyName a]
$creadListPrec :: forall k (a :: k). ReadPrec [MyName a]
readPrec :: ReadPrec (MyName a)
$creadPrec :: forall k (a :: k). ReadPrec (MyName a)
readList :: ReadS [MyName a]
$creadList :: forall k (a :: k). ReadS [MyName a]
readsPrec :: Int -> ReadS (MyName a)
$creadsPrec :: forall k (a :: k). Int -> ReadS (MyName a)
Read, MyName a -> MyName a -> Bool
(MyName a -> MyName a -> Bool)
-> (MyName a -> MyName a -> Bool) -> Eq (MyName a)
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
forall k (a :: k). MyName a -> MyName a -> Bool
/= :: MyName a -> MyName a -> Bool
$c/= :: forall k (a :: k). MyName a -> MyName a -> Bool
== :: MyName a -> MyName a -> Bool
$c== :: forall k (a :: k). MyName a -> MyName a -> Bool
Eq, Eq (MyName a)
Eq (MyName a)
-> (MyName a -> MyName a -> Ordering)
-> (MyName a -> MyName a -> Bool)
-> (MyName a -> MyName a -> Bool)
-> (MyName a -> MyName a -> Bool)
-> (MyName a -> MyName a -> Bool)
-> (MyName a -> MyName a -> MyName a)
-> (MyName a -> MyName a -> MyName a)
-> Ord (MyName a)
MyName a -> MyName a -> Bool
MyName a -> MyName a -> Ordering
MyName a -> MyName a -> MyName a
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
forall k (a :: k). Eq (MyName a)
forall k (a :: k). MyName a -> MyName a -> Bool
forall k (a :: k). MyName a -> MyName a -> Ordering
forall k (a :: k). MyName a -> MyName a -> MyName a
min :: MyName a -> MyName a -> MyName a
$cmin :: forall k (a :: k). MyName a -> MyName a -> MyName a
max :: MyName a -> MyName a -> MyName a
$cmax :: forall k (a :: k). MyName a -> MyName a -> MyName a
>= :: MyName a -> MyName a -> Bool
$c>= :: forall k (a :: k). MyName a -> MyName a -> Bool
> :: MyName a -> MyName a -> Bool
$c> :: forall k (a :: k). MyName a -> MyName a -> Bool
<= :: MyName a -> MyName a -> Bool
$c<= :: forall k (a :: k). MyName a -> MyName a -> Bool
< :: MyName a -> MyName a -> Bool
$c< :: forall k (a :: k). MyName a -> MyName a -> Bool
compare :: MyName a -> MyName a -> Ordering
$ccompare :: forall k (a :: k). MyName a -> MyName a -> Ordering
$cp1Ord :: forall k (a :: k). Eq (MyName a)
Ord, (forall x. MyName a -> Rep (MyName a) x)
-> (forall x. Rep (MyName a) x -> MyName a) -> Generic (MyName a)
forall x. Rep (MyName a) x -> MyName a
forall x. MyName a -> Rep (MyName a) x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
forall k (a :: k) x. Rep (MyName a) x -> MyName a
forall k (a :: k) x. MyName a -> Rep (MyName a) x
$cto :: forall k (a :: k) x. Rep (MyName a) x -> MyName a
$cfrom :: forall k (a :: k) x. MyName a -> Rep (MyName a) x
Generic)
  deriving anyclass (MyName a -> ()
(MyName a -> ()) -> NFData (MyName a)
forall a. (a -> ()) -> NFData a
forall k (a :: k). MyName a -> ()
rnf :: MyName a -> ()
$crnf :: forall k (a :: k). MyName a -> ()
NFData)

instance IsString (MyName a) where
  fromString :: String -> MyName a
fromString = String -> MyName a
forall k (a :: k). String -> MyName a
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.
  toArchLinuxRep :: a -> MyName 'ArchLinuxRep

instance HasMyName (MyName 'ArchLinuxRep) where
  toHackageRep :: MyName 'ArchLinuxRep -> MyName 'HackageRep
toHackageRep = ArchLinuxName -> MyName 'HackageRep
forall a. HasMyName a => a -> MyName 'HackageRep
toHackageRep (ArchLinuxName -> MyName 'HackageRep)
-> (MyName 'ArchLinuxRep -> ArchLinuxName)
-> MyName 'ArchLinuxRep
-> MyName 'HackageRep
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> ArchLinuxName
ArchLinuxName (String -> ArchLinuxName)
-> (MyName 'ArchLinuxRep -> String)
-> MyName 'ArchLinuxRep
-> ArchLinuxName
forall b c a. (b -> c) -> (a -> b) -> a -> c
. MyName 'ArchLinuxRep -> String
forall k (a :: k). MyName a -> String
unsafeUnMyName
  toArchLinuxRep :: MyName 'ArchLinuxRep -> MyName 'ArchLinuxRep
toArchLinuxRep = MyName 'ArchLinuxRep -> MyName 'ArchLinuxRep
forall a. a -> a
id

instance HasMyName (MyName 'HackageRep) where
  toHackageRep :: MyName 'HackageRep -> MyName 'HackageRep
toHackageRep = MyName 'HackageRep -> MyName 'HackageRep
forall a. a -> a
id
  toArchLinuxRep :: MyName 'HackageRep -> MyName 'ArchLinuxRep
toArchLinuxRep = PackageName -> MyName 'ArchLinuxRep
forall a. HasMyName a => a -> MyName 'ArchLinuxRep
toArchLinuxRep (PackageName -> MyName 'ArchLinuxRep)
-> (MyName 'HackageRep -> PackageName)
-> MyName 'HackageRep
-> MyName 'ArchLinuxRep
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> PackageName
mkPackageName (String -> PackageName)
-> (MyName 'HackageRep -> String)
-> MyName 'HackageRep
-> PackageName
forall b c a. (b -> c) -> (a -> b) -> a -> c
. MyName 'HackageRep -> String
forall k (a :: k). MyName a -> String
unsafeUnMyName

instance HasMyName PackageName where
  toHackageRep :: PackageName -> MyName 'HackageRep
toHackageRep = String -> MyName 'HackageRep
forall k (a :: k). String -> MyName a
MyName (String -> MyName 'HackageRep)
-> (PackageName -> String) -> PackageName -> MyName 'HackageRep
forall b c a. (b -> c) -> (a -> b) -> a -> c
. PackageName -> String
unPackageName
  toArchLinuxRep :: PackageName -> MyName 'ArchLinuxRep
toArchLinuxRep = String -> MyName 'ArchLinuxRep
go (String -> MyName 'ArchLinuxRep)
-> (PackageName -> String) -> PackageName -> MyName 'ArchLinuxRep
forall b c a. (b -> c) -> (a -> b) -> a -> c
. PackageName -> String
unPackageName
    where
      go :: String -> MyName 'ArchLinuxRep
go String
s = case MyName 'HackageRep -> Maybe (MyName 'ArchLinuxRep)
hackageToCommunityP (String -> MyName 'HackageRep
forall k (a :: k). String -> MyName a
MyName String
s) of
        Just MyName 'ArchLinuxRep
x -> MyName 'ArchLinuxRep
x
        Maybe (MyName 'ArchLinuxRep)
_ ->
          String -> MyName 'ArchLinuxRep
forall k (a :: k). String -> MyName a
MyName (String -> MyName 'ArchLinuxRep)
-> ShowS -> String -> MyName 'ArchLinuxRep
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Char -> Char) -> ShowS
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Char -> Char
toLower (String -> MyName 'ArchLinuxRep) -> String -> MyName 'ArchLinuxRep
forall a b. (a -> b) -> a -> b
$
            ( if String
"haskell-" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` String
s
                then String
s
                else String
"haskell-" String -> ShowS
forall a. Semigroup a => a -> a -> a
<> String
s
            )

instance HasMyName ArchLinuxName where
  toHackageRep :: ArchLinuxName -> MyName 'HackageRep
toHackageRep = String -> MyName 'HackageRep
go (String -> MyName 'HackageRep)
-> (ArchLinuxName -> String) -> ArchLinuxName -> MyName 'HackageRep
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ArchLinuxName -> String
unArchLinuxName
    where
      go :: String -> MyName 'HackageRep
go String
s = case MyName 'ArchLinuxRep -> Maybe (MyName 'HackageRep)
communityToHackageP (String -> MyName 'ArchLinuxRep
forall k (a :: k). String -> MyName a
MyName String
s) of
        Just MyName 'HackageRep
x -> MyName 'HackageRep
x
        Maybe (MyName 'HackageRep)
_ -> String -> MyName 'HackageRep
forall k (a :: k). String -> MyName a
MyName (String -> MyName 'HackageRep) -> String -> MyName 'HackageRep
forall a b. (a -> b) -> a -> b
$ Int -> ShowS
forall a. Int -> [a] -> [a]
drop Int
8 String
s
  toArchLinuxRep :: ArchLinuxName -> MyName 'ArchLinuxRep
toArchLinuxRep = String -> MyName 'ArchLinuxRep
forall k (a :: k). String -> MyName a
MyName (String -> MyName 'ArchLinuxRep)
-> (ArchLinuxName -> String)
-> ArchLinuxName
-> MyName 'ArchLinuxRep
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ArchLinuxName -> String
unArchLinuxName

-- | Back to 'ArchLinuxName'.
mToArchLinuxName :: MyName 'ArchLinuxRep -> ArchLinuxName
mToArchLinuxName :: MyName 'ArchLinuxRep -> ArchLinuxName
mToArchLinuxName = String -> ArchLinuxName
ArchLinuxName (String -> ArchLinuxName)
-> (MyName 'ArchLinuxRep -> String)
-> MyName 'ArchLinuxRep
-> ArchLinuxName
forall b c a. (b -> c) -> (a -> b) -> a -> c
. MyName 'ArchLinuxRep -> String
forall k (a :: k). MyName a -> String
unsafeUnMyName

-- | Back to 'PackageName'.
mToHackageName :: MyName 'HackageRep -> PackageName
mToHackageName :: MyName 'HackageRep -> PackageName
mToHackageName = String -> PackageName
mkPackageName (String -> PackageName)
-> (MyName 'HackageRep -> String)
-> MyName 'HackageRep
-> PackageName
forall b c a. (b -> c) -> (a -> b) -> a -> c
. MyName 'HackageRep -> String
forall k (a :: k). MyName a -> String
unsafeUnMyName

-- | Convert @n@ to 'ArchLinuxName'.
toArchLinuxName :: HasMyName n => n -> ArchLinuxName
toArchLinuxName :: n -> ArchLinuxName
toArchLinuxName = MyName 'ArchLinuxRep -> ArchLinuxName
mToArchLinuxName (MyName 'ArchLinuxRep -> ArchLinuxName)
-> (n -> MyName 'ArchLinuxRep) -> n -> ArchLinuxName
forall b c a. (b -> c) -> (a -> b) -> a -> c
. n -> MyName 'ArchLinuxRep
forall a. HasMyName a => a -> MyName 'ArchLinuxRep
toArchLinuxRep

-- | Convert @n@ to 'PackageName'.
toHackageName :: HasMyName n => n -> PackageName
toHackageName :: n -> PackageName
toHackageName = MyName 'HackageRep -> PackageName
mToHackageName (MyName 'HackageRep -> PackageName)
-> (n -> MyName 'HackageRep) -> n -> PackageName
forall b c a. (b -> c) -> (a -> b) -> a -> c
. n -> MyName 'HackageRep
forall a. HasMyName a => a -> MyName 'HackageRep
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 :: ArchLinuxName -> Bool
isHaskellPackage :: ArchLinuxName -> Bool
isHaskellPackage ArchLinuxName
name =
  let rep :: MyName 'ArchLinuxRep
rep = ArchLinuxName -> MyName 'ArchLinuxRep
forall a. HasMyName a => a -> MyName 'ArchLinuxRep
toArchLinuxRep ArchLinuxName
name
   in (MyName 'ArchLinuxRep
rep MyName 'ArchLinuxRep -> [MyName 'ArchLinuxRep] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` [MyName 'ArchLinuxRep]
communityListP Bool -> Bool -> Bool
|| String
"haskell-" String -> String -> Bool
forall a. Eq a => [a] -> [a] -> Bool
`isPrefixOf` MyName 'ArchLinuxRep -> String
forall k (a :: k). MyName a -> String
unsafeUnMyName MyName 'ArchLinuxRep
rep) Bool -> Bool -> Bool
&& MyName 'ArchLinuxRep
rep MyName 'ArchLinuxRep -> [MyName 'ArchLinuxRep] -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`notElem` [MyName 'ArchLinuxRep]
falseListP