{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}

-- | Template name handling.


module Stack.Types.TemplateName
  ( TemplateName
  , RepoTemplatePath (..)
  , RepoService (..)
  , TemplatePath (..)
  , templateName
  , templatePath
  , parseTemplateNameFromString
  , parseRepoPathWithService
  , templateNameArgument
  , templateParamArgument
  , defaultTemplateName
  ) where

import           Data.Aeson ( FromJSON (..), withText )
import qualified Data.Text as T
import           Network.HTTP.StackClient ( parseRequest )
import qualified Options.Applicative as O
import           Path
import           Stack.Prelude

-- | Type representing exceptions thrown by functions exported by the

-- "Stack.Types.TemplateName" module.

newtype TypeTemplateNameException
    = DefaultTemplateNameNotParsedBug String
    deriving (Int -> TypeTemplateNameException -> ShowS
[TypeTemplateNameException] -> ShowS
TypeTemplateNameException -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [TypeTemplateNameException] -> ShowS
$cshowList :: [TypeTemplateNameException] -> ShowS
show :: TypeTemplateNameException -> String
$cshow :: TypeTemplateNameException -> String
showsPrec :: Int -> TypeTemplateNameException -> ShowS
$cshowsPrec :: Int -> TypeTemplateNameException -> ShowS
Show, Typeable)

instance Exception TypeTemplateNameException where
    displayException :: TypeTemplateNameException -> String
displayException (DefaultTemplateNameNotParsedBug String
s) = String -> ShowS
bugReport String
"[S-7410]" forall a b. (a -> b) -> a -> b
$
        String
"The impossible happened! Cannot parse default template name: "
        forall a. [a] -> [a] -> [a]
++ String
s

-- | A template name.

data TemplateName = TemplateName !Text !TemplatePath
  deriving (Eq TemplateName
TemplateName -> TemplateName -> Bool
TemplateName -> TemplateName -> Ordering
TemplateName -> TemplateName -> TemplateName
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
min :: TemplateName -> TemplateName -> TemplateName
$cmin :: TemplateName -> TemplateName -> TemplateName
max :: TemplateName -> TemplateName -> TemplateName
$cmax :: TemplateName -> TemplateName -> TemplateName
>= :: TemplateName -> TemplateName -> Bool
$c>= :: TemplateName -> TemplateName -> Bool
> :: TemplateName -> TemplateName -> Bool
$c> :: TemplateName -> TemplateName -> Bool
<= :: TemplateName -> TemplateName -> Bool
$c<= :: TemplateName -> TemplateName -> Bool
< :: TemplateName -> TemplateName -> Bool
$c< :: TemplateName -> TemplateName -> Bool
compare :: TemplateName -> TemplateName -> Ordering
$ccompare :: TemplateName -> TemplateName -> Ordering
Ord,TemplateName -> TemplateName -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: TemplateName -> TemplateName -> Bool
$c/= :: TemplateName -> TemplateName -> Bool
== :: TemplateName -> TemplateName -> Bool
$c== :: TemplateName -> TemplateName -> Bool
Eq,Int -> TemplateName -> ShowS
[TemplateName] -> ShowS
TemplateName -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [TemplateName] -> ShowS
$cshowList :: [TemplateName] -> ShowS
show :: TemplateName -> String
$cshow :: TemplateName -> String
showsPrec :: Int -> TemplateName -> ShowS
$cshowsPrec :: Int -> TemplateName -> ShowS
Show)

data TemplatePath = AbsPath (Path Abs File)
                  -- ^ an absolute path on the filesystem

                  | RelPath String (Path Rel File)
                  -- ^ a relative path on the filesystem, or relative to

                  -- the template repository. To avoid path separator conversion

                  -- on Windows, the raw command-line parameter passed is also

                  -- given as the first field (possibly with @.hsfiles@ appended).

                  | UrlPath String
                  -- ^ a full URL

                  | RepoPath RepoTemplatePath
  deriving (TemplatePath -> TemplatePath -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: TemplatePath -> TemplatePath -> Bool
$c/= :: TemplatePath -> TemplatePath -> Bool
== :: TemplatePath -> TemplatePath -> Bool
$c== :: TemplatePath -> TemplatePath -> Bool
Eq, Eq TemplatePath
TemplatePath -> TemplatePath -> Bool
TemplatePath -> TemplatePath -> Ordering
TemplatePath -> TemplatePath -> TemplatePath
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
min :: TemplatePath -> TemplatePath -> TemplatePath
$cmin :: TemplatePath -> TemplatePath -> TemplatePath
max :: TemplatePath -> TemplatePath -> TemplatePath
$cmax :: TemplatePath -> TemplatePath -> TemplatePath
>= :: TemplatePath -> TemplatePath -> Bool
$c>= :: TemplatePath -> TemplatePath -> Bool
> :: TemplatePath -> TemplatePath -> Bool
$c> :: TemplatePath -> TemplatePath -> Bool
<= :: TemplatePath -> TemplatePath -> Bool
$c<= :: TemplatePath -> TemplatePath -> Bool
< :: TemplatePath -> TemplatePath -> Bool
$c< :: TemplatePath -> TemplatePath -> Bool
compare :: TemplatePath -> TemplatePath -> Ordering
$ccompare :: TemplatePath -> TemplatePath -> Ordering
Ord, Int -> TemplatePath -> ShowS
[TemplatePath] -> ShowS
TemplatePath -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [TemplatePath] -> ShowS
$cshowList :: [TemplatePath] -> ShowS
show :: TemplatePath -> String
$cshow :: TemplatePath -> String
showsPrec :: Int -> TemplatePath -> ShowS
$cshowsPrec :: Int -> TemplatePath -> ShowS
Show)

-- | Details for how to access a template from a remote repo.

data RepoTemplatePath = RepoTemplatePath
    { RepoTemplatePath -> RepoService
rtpService  :: RepoService
    , RepoTemplatePath -> Text
rtpUser     :: Text
    , RepoTemplatePath -> Text
rtpTemplate :: Text
    }
    deriving (RepoTemplatePath -> RepoTemplatePath -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: RepoTemplatePath -> RepoTemplatePath -> Bool
$c/= :: RepoTemplatePath -> RepoTemplatePath -> Bool
== :: RepoTemplatePath -> RepoTemplatePath -> Bool
$c== :: RepoTemplatePath -> RepoTemplatePath -> Bool
Eq, Eq RepoTemplatePath
RepoTemplatePath -> RepoTemplatePath -> Bool
RepoTemplatePath -> RepoTemplatePath -> Ordering
RepoTemplatePath -> RepoTemplatePath -> RepoTemplatePath
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
min :: RepoTemplatePath -> RepoTemplatePath -> RepoTemplatePath
$cmin :: RepoTemplatePath -> RepoTemplatePath -> RepoTemplatePath
max :: RepoTemplatePath -> RepoTemplatePath -> RepoTemplatePath
$cmax :: RepoTemplatePath -> RepoTemplatePath -> RepoTemplatePath
>= :: RepoTemplatePath -> RepoTemplatePath -> Bool
$c>= :: RepoTemplatePath -> RepoTemplatePath -> Bool
> :: RepoTemplatePath -> RepoTemplatePath -> Bool
$c> :: RepoTemplatePath -> RepoTemplatePath -> Bool
<= :: RepoTemplatePath -> RepoTemplatePath -> Bool
$c<= :: RepoTemplatePath -> RepoTemplatePath -> Bool
< :: RepoTemplatePath -> RepoTemplatePath -> Bool
$c< :: RepoTemplatePath -> RepoTemplatePath -> Bool
compare :: RepoTemplatePath -> RepoTemplatePath -> Ordering
$ccompare :: RepoTemplatePath -> RepoTemplatePath -> Ordering
Ord, Int -> RepoTemplatePath -> ShowS
[RepoTemplatePath] -> ShowS
RepoTemplatePath -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [RepoTemplatePath] -> ShowS
$cshowList :: [RepoTemplatePath] -> ShowS
show :: RepoTemplatePath -> String
$cshow :: RepoTemplatePath -> String
showsPrec :: Int -> RepoTemplatePath -> ShowS
$cshowsPrec :: Int -> RepoTemplatePath -> ShowS
Show)

-- | Services from which templates can be retrieved from a repository.

data RepoService = GitHub | GitLab | Bitbucket
    deriving (RepoService -> RepoService -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: RepoService -> RepoService -> Bool
$c/= :: RepoService -> RepoService -> Bool
== :: RepoService -> RepoService -> Bool
$c== :: RepoService -> RepoService -> Bool
Eq, Eq RepoService
RepoService -> RepoService -> Bool
RepoService -> RepoService -> Ordering
RepoService -> RepoService -> RepoService
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
min :: RepoService -> RepoService -> RepoService
$cmin :: RepoService -> RepoService -> RepoService
max :: RepoService -> RepoService -> RepoService
$cmax :: RepoService -> RepoService -> RepoService
>= :: RepoService -> RepoService -> Bool
$c>= :: RepoService -> RepoService -> Bool
> :: RepoService -> RepoService -> Bool
$c> :: RepoService -> RepoService -> Bool
<= :: RepoService -> RepoService -> Bool
$c<= :: RepoService -> RepoService -> Bool
< :: RepoService -> RepoService -> Bool
$c< :: RepoService -> RepoService -> Bool
compare :: RepoService -> RepoService -> Ordering
$ccompare :: RepoService -> RepoService -> Ordering
Ord, Int -> RepoService -> ShowS
[RepoService] -> ShowS
RepoService -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [RepoService] -> ShowS
$cshowList :: [RepoService] -> ShowS
show :: RepoService -> String
$cshow :: RepoService -> String
showsPrec :: Int -> RepoService -> ShowS
$cshowsPrec :: Int -> RepoService -> ShowS
Show)

instance FromJSON TemplateName where
    parseJSON :: Value -> Parser TemplateName
parseJSON = forall a. String -> (Text -> Parser a) -> Value -> Parser a
withText String
"TemplateName" forall a b. (a -> b) -> a -> b
$
        forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either forall (m :: * -> *) a. MonadFail m => String -> m a
fail forall (f :: * -> *) a. Applicative f => a -> f a
pure forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Either String TemplateName
parseTemplateNameFromString forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> String
T.unpack

-- | An argument which accepts a template name of the format

-- @foo.hsfiles@ or @foo@, ultimately normalized to @foo@.

templateNameArgument :: O.Mod O.ArgumentFields TemplateName
                     -> O.Parser TemplateName
templateNameArgument :: Mod ArgumentFields TemplateName -> Parser TemplateName
templateNameArgument =
    forall a. ReadM a -> Mod ArgumentFields a -> Parser a
O.argument
        (do String
s <- forall s. IsString s => ReadM s
O.str
            forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either forall a. String -> ReadM a
O.readerError forall (f :: * -> *) a. Applicative f => a -> f a
pure (String -> Either String TemplateName
parseTemplateNameFromString String
s))

-- | An argument which accepts a @key:value@ pair for specifying parameters.

templateParamArgument :: O.Mod O.OptionFields (Text,Text)
                      -> O.Parser (Text,Text)
templateParamArgument :: Mod OptionFields (Text, Text) -> Parser (Text, Text)
templateParamArgument =
    forall a. ReadM a -> Mod OptionFields a -> Parser a
O.option
        (do String
s <- forall s. IsString s => ReadM s
O.str
            forall a c b. (a -> c) -> (b -> c) -> Either a b -> c
either forall a. String -> ReadM a
O.readerError forall (f :: * -> *) a. Applicative f => a -> f a
pure (String -> Either String (Text, Text)
parsePair String
s))
  where
    parsePair :: String -> Either String (Text, Text)
    parsePair :: String -> Either String (Text, Text)
parsePair String
s =
        case forall a. (a -> Bool) -> [a] -> ([a], [a])
break (forall a. Eq a => a -> a -> Bool
==Char
':') String
s of
            (String
key,Char
':':value :: String
value@(Char
_:String
_)) -> forall a b. b -> Either a b
Right (String -> Text
T.pack String
key, String -> Text
T.pack String
value)
            (String, String)
_ -> forall a b. a -> Either a b
Left (String
"Expected key:value format for argument: " forall a. Semigroup a => a -> a -> a
<> String
s)

-- | Parse a template name from a string.

parseTemplateNameFromString :: String -> Either String TemplateName
parseTemplateNameFromString :: String -> Either String TemplateName
parseTemplateNameFromString String
fname =
    case Text -> Text -> Maybe Text
T.stripSuffix Text
".hsfiles" (String -> Text
T.pack String
fname) of
        Maybe Text
Nothing -> Text -> String -> String -> Either String TemplateName
parseValidFile (String -> Text
T.pack String
fname) (String
fname forall a. Semigroup a => a -> a -> a
<> String
".hsfiles") String
fname
        Just Text
prefix -> Text -> String -> String -> Either String TemplateName
parseValidFile Text
prefix String
fname String
fname
  where
    parseValidFile :: Text -> String -> String -> Either String TemplateName
parseValidFile Text
prefix String
hsf String
orig = forall b a. b -> (a -> b) -> Maybe a -> b
maybe (forall a b. a -> Either a b
Left String
expected) forall a b. b -> Either a b
Right
                                           forall a b. (a -> b) -> a -> b
$ forall (t :: * -> *) (f :: * -> *) a.
(Foldable t, Alternative f) =>
t (f a) -> f a
asum (Text -> String -> String -> [Maybe TemplateName]
validParses Text
prefix String
hsf String
orig)
    validParses :: Text -> String -> String -> [Maybe TemplateName]
validParses Text
prefix String
hsf String
orig =
        -- NOTE: order is important

        [ Text -> TemplatePath -> TemplateName
TemplateName Text
prefix        forall b c a. (b -> c) -> (a -> b) -> a -> c
. RepoTemplatePath -> TemplatePath
RepoPath forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> String -> Maybe RepoTemplatePath
parseRepoPath String
hsf
        , Text -> TemplatePath -> TemplateName
TemplateName (String -> Text
T.pack String
orig) forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> TemplatePath
UrlPath forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> (forall (m :: * -> *). MonadThrow m => String -> m Request
parseRequest String
orig forall (f :: * -> *) a b. Applicative f => f a -> f b -> f b
*> forall a. a -> Maybe a
Just String
orig)
        , Text -> TemplatePath -> TemplateName
TemplateName Text
prefix        forall b c a. (b -> c) -> (a -> b) -> a -> c
. Path Abs File -> TemplatePath
AbsPath forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall (m :: * -> *). MonadThrow m => String -> m (Path Abs File)
parseAbsFile String
hsf
        , Text -> TemplatePath -> TemplateName
TemplateName Text
prefix        forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Path Rel File -> TemplatePath
RelPath String
hsf forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> forall (m :: * -> *). MonadThrow m => String -> m (Path Rel File)
parseRelFile String
hsf
        ]
    expected :: String
expected = String
"Expected a template like: foo or foo.hsfiles or\
               \ https://example.com/foo.hsfiles or github:user/foo"

-- | The default template name you can use if you don't have one.

defaultTemplateName :: TemplateName
defaultTemplateName :: TemplateName
defaultTemplateName =
  case String -> Either String TemplateName
parseTemplateNameFromString String
"new-template" of
    Left String
s -> forall e a. Exception e => e -> a
impureThrow forall a b. (a -> b) -> a -> b
$ String -> TypeTemplateNameException
DefaultTemplateNameNotParsedBug String
s
    Right TemplateName
x -> TemplateName
x

-- | Get a text representation of the template name.

templateName :: TemplateName -> Text
templateName :: TemplateName -> Text
templateName (TemplateName Text
prefix TemplatePath
_) = Text
prefix

-- | Get the path of the template.

templatePath :: TemplateName -> TemplatePath
templatePath :: TemplateName -> TemplatePath
templatePath (TemplateName Text
_ TemplatePath
fp) = TemplatePath
fp

defaultRepoUserForService :: RepoService -> Maybe Text
defaultRepoUserForService :: RepoService -> Maybe Text
defaultRepoUserForService RepoService
GitHub = forall a. a -> Maybe a
Just Text
"commercialhaskell"
defaultRepoUserForService RepoService
_      = forall a. Maybe a
Nothing

-- | Parses a template path of the form @github:user/template@.

parseRepoPath :: String -> Maybe RepoTemplatePath
parseRepoPath :: String -> Maybe RepoTemplatePath
parseRepoPath String
s =
  case Text -> Text -> [Text]
T.splitOn Text
":" (String -> Text
T.pack String
s) of
    [Text
"github"    , Text
rest] -> RepoService -> Text -> Maybe RepoTemplatePath
parseRepoPathWithService RepoService
GitHub Text
rest
    [Text
"gitlab"    , Text
rest] -> RepoService -> Text -> Maybe RepoTemplatePath
parseRepoPathWithService RepoService
GitLab Text
rest
    [Text
"bitbucket" , Text
rest] -> RepoService -> Text -> Maybe RepoTemplatePath
parseRepoPathWithService RepoService
Bitbucket Text
rest
    [Text]
_                    -> forall a. Maybe a
Nothing

-- | Parses a template path of the form @user/template@, given a service

parseRepoPathWithService :: RepoService -> Text -> Maybe RepoTemplatePath
parseRepoPathWithService :: RepoService -> Text -> Maybe RepoTemplatePath
parseRepoPathWithService RepoService
service Text
path =
  case Text -> Text -> [Text]
T.splitOn Text
"/" Text
path of
    [Text
user, Text
name] -> forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ RepoService -> Text -> Text -> RepoTemplatePath
RepoTemplatePath RepoService
service Text
user Text
name
    [Text
name]       -> do
        Text
repoUser <- RepoService -> Maybe Text
defaultRepoUserForService RepoService
service
        forall a. a -> Maybe a
Just forall a b. (a -> b) -> a -> b
$ RepoService -> Text -> Text -> RepoTemplatePath
RepoTemplatePath RepoService
service Text
repoUser Text
name
    [Text]
_            -> forall a. Maybe a
Nothing