{-# LANGUAGE ApplicativeDo #-}
{-# LANGUAGE BlockArguments #-}
{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
module Hercules.CLI.State (commandParser, getProjectAndClient) where
import Conduit (mapC, runConduitRes, sinkLazyBuilder, (.|))
import qualified Data.ByteString.Builder as BB
import qualified Data.ByteString.Lazy as BL
import Data.Has (Has)
import Hercules.API (ClientAuth, NoContent, enterApiE)
import Hercules.API.Name (Name (Name))
import Hercules.API.State
import Hercules.CLI.Client
import Hercules.CLI.Common (runAuthenticated)
import Hercules.CLI.Options (mkCommand, subparser)
import Hercules.CLI.Project (ProjectPath (projectPathOwner, projectPathProject, projectPathSite), findProjectContextually, projectOption)
import Options.Applicative (auto, bashCompleter, completer, help, long, metavar, option, strOption)
import qualified Options.Applicative as Optparse
import Protolude
import RIO (RIO)
import qualified RIO.ByteString as BS
import Servant.API (HList (HCons), Headers (Headers), ResponseHeader (Header), fromSourceIO)
import Servant.Client (ClientError (ConnectionError))
import Servant.Client.Generic (AsClientT)
import Servant.Client.Internal.HttpClient.Streaming (ClientM)
import Servant.Conduit ()
import qualified Servant.Types.SourceT as Servant
commandParser, getCommandParser, putCommandParser :: Optparse.Parser (IO ())
commandParser :: Parser (IO ())
commandParser =
Mod CommandFields (IO ()) -> Parser (IO ())
forall a. Mod CommandFields a -> Parser a
subparser
( FilePath
-> InfoMod (IO ()) -> Parser (IO ()) -> Mod CommandFields (IO ())
forall a. FilePath -> InfoMod a -> Parser a -> Mod CommandFields a
mkCommand
FilePath
"get"
(FilePath -> InfoMod (IO ())
forall a. FilePath -> InfoMod a
Optparse.progDesc FilePath
"Download a state file")
Parser (IO ())
getCommandParser
Mod CommandFields (IO ())
-> Mod CommandFields (IO ()) -> Mod CommandFields (IO ())
forall a. Semigroup a => a -> a -> a
<> FilePath
-> InfoMod (IO ()) -> Parser (IO ()) -> Mod CommandFields (IO ())
forall a. FilePath -> InfoMod a -> Parser a -> Mod CommandFields a
mkCommand
FilePath
"put"
(FilePath -> InfoMod (IO ())
forall a. FilePath -> InfoMod a
Optparse.progDesc FilePath
"Upload a state file")
Parser (IO ())
putCommandParser
)
getCommandParser :: Parser (IO ())
getCommandParser = do
Maybe ProjectPath
projectMaybe <- Parser ProjectPath -> Parser (Maybe ProjectPath)
forall (f :: * -> *) a. Alternative f => f a -> f (Maybe a)
optional Parser ProjectPath
projectOption
Text
name <- Parser Text
nameOption
FilePath
file <- Parser FilePath
fileOption
Maybe Int
versionMaybe <- Parser Int -> Parser (Maybe Int)
forall (f :: * -> *) a. Alternative f => f a -> f (Maybe a)
optional Parser Int
versionOption
pure do
RIO (HerculesClientToken, HerculesClientEnv) () -> IO ()
forall b. RIO (HerculesClientToken, HerculesClientEnv) b -> IO b
runAuthenticated do
ProjectStateResourceGroup ClientAuth (AsClientT ClientM)
projectStateClient <- Maybe ProjectPath
-> RIO
(HerculesClientToken, HerculesClientEnv)
(ProjectStateResourceGroup ClientAuth (AsClientT ClientM))
forall r.
(Has HerculesClientToken r, Has HerculesClientEnv r) =>
Maybe ProjectPath
-> RIO r (ProjectStateResourceGroup ClientAuth (AsClientT ClientM))
getProjectAndClient Maybe ProjectPath
projectMaybe
ByteString
bytes <- Text
-> (Token
-> ClientM
(Headers '[ContentLength, ContentDisposition] (SourceIO RawBytes)))
-> (Either
ClientError
(Headers '[ContentLength, ContentDisposition] (SourceIO RawBytes))
-> IO ByteString)
-> RIO (HerculesClientToken, HerculesClientEnv) ByteString
forall r b c.
(Has HerculesClientToken r, Has HerculesClientEnv r) =>
Text
-> (Token -> ClientM b)
-> (Either ClientError b -> IO c)
-> RIO r c
retryStreamOnFail Text
"state get" (\Token
token -> ProjectStateResourceGroup ClientAuth (AsClientT ClientM)
-> AsClientT ClientM
:- (Summary "Download a state file"
:> ("state"
:> (Capture' '[Required, Strict] "stateName" Text
:> ("data"
:> (QueryParam' '[Optional, Strict] "version" Int
:> (ClientAuth
:> StreamGet
NoFraming
OctetStream
(Headers
'[ContentLength, ContentDisposition] (SourceIO RawBytes))))))))
forall auth f.
ProjectStateResourceGroup auth f
-> f
:- (Summary "Download a state file"
:> ("state"
:> (Capture' '[Required, Strict] "stateName" Text
:> ("data"
:> (QueryParam' '[Optional, Strict] "version" Int
:> (auth
:> StreamGet
NoFraming
OctetStream
(Headers
'[ContentLength, ContentDisposition] (SourceIO RawBytes))))))))
getStateData ProjectStateResourceGroup ClientAuth (AsClientT ClientM)
projectStateClient Text
name Maybe Int
versionMaybe Token
token) \case
Left ClientError
e -> ClientError -> IO ByteString
forall (m :: * -> *) e a. (MonadIO m, Exception e) => e -> m a
throwIO ClientError
e
Right (Headers SourceIO RawBytes
stream HList '[ContentLength, ContentDisposition]
headers) -> do
#if MIN_VERSION_servant(0,20,0)
s <- fromSourceIO stream
#else
let s :: ConduitT () RawBytes (ResourceT IO) ()
s = SourceIO RawBytes -> ConduitT () RawBytes (ResourceT IO) ()
forall chunk a. FromSourceIO chunk a => SourceIO chunk -> a
fromSourceIO SourceIO RawBytes
stream
#endif
ByteString
bl <- ConduitT () Void (ResourceT IO) ByteString -> IO ByteString
forall (m :: * -> *) r.
MonadUnliftIO m =>
ConduitT () Void (ResourceT m) r -> m r
runConduitRes (ConduitT () Void (ResourceT IO) ByteString -> IO ByteString)
-> ConduitT () Void (ResourceT IO) ByteString -> IO ByteString
forall a b. (a -> b) -> a -> b
$ ConduitT () RawBytes (ResourceT IO) ()
s ConduitT () RawBytes (ResourceT IO) ()
-> ConduitT RawBytes Void (ResourceT IO) ByteString
-> ConduitT () Void (ResourceT IO) ByteString
forall (m :: * -> *) a b c r.
Monad m =>
ConduitT a b m () -> ConduitT b c m r -> ConduitT a c m r
.| (RawBytes -> Builder)
-> ConduitT RawBytes Builder (ResourceT IO) ()
forall (m :: * -> *) a b. Monad m => (a -> b) -> ConduitT a b m ()
mapC (ByteString -> Builder
BB.byteString (ByteString -> Builder)
-> (RawBytes -> ByteString) -> RawBytes -> Builder
forall b c a. (b -> c) -> (a -> b) -> a -> c
. RawBytes -> ByteString
fromRawBytes) ConduitT RawBytes Builder (ResourceT IO) ()
-> ConduitT Builder Void (ResourceT IO) ByteString
-> ConduitT RawBytes Void (ResourceT IO) ByteString
forall (m :: * -> *) a b c r.
Monad m =>
ConduitT a b m () -> ConduitT b c m r -> ConduitT a c m r
.| ConduitT Builder Void (ResourceT IO) ByteString
forall (m :: * -> *) o. Monad m => ConduitT Builder o m ByteString
sinkLazyBuilder
let lenH :: ResponseHeader "Content-Length" Integer
ResponseHeader h x
ResponseHeader "Content-Length" Integer
lenH `HCons` HList xs
_ = HList '[ContentLength, ContentDisposition]
headers
actual :: Integer
actual = Int64 -> Integer
forall a b. (Integral a, Num b) => a -> b
fromIntegral (Int64 -> Integer) -> Int64 -> Integer
forall a b. (a -> b) -> a -> b
$ ByteString -> Int64
BL.length ByteString
bl
case ResponseHeader "Content-Length" Integer
lenH of
Header Integer
expected ->
Bool -> IO () -> IO ()
forall (f :: * -> *). Applicative f => Bool -> f () -> f ()
when (Integer
actual Integer -> Integer -> Bool
forall a. Eq a => a -> a -> Bool
/= Integer
expected) do
ClientError -> IO ()
forall (m :: * -> *) e a. (MonadIO m, Exception e) => e -> m a
throwIO (ClientError -> IO ()) -> ClientError -> IO ()
forall a b. (a -> b) -> a -> b
$ SomeException -> ClientError
ConnectionError (SomeException -> ClientError) -> SomeException -> ClientError
forall a b. (a -> b) -> a -> b
$ FatalError -> SomeException
forall e. Exception e => e -> SomeException
toException (FatalError -> SomeException) -> FatalError -> SomeException
forall a b. (a -> b) -> a -> b
$ Text -> FatalError
FatalError (Text -> FatalError) -> Text -> FatalError
forall a b. (a -> b) -> a -> b
$ Text
"Expected " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Integer -> Text
forall a b. (Show a, StringConv FilePath b) => a -> b
show Integer
expected Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
" bytes, but got " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Integer -> Text
forall a b. (Show a, StringConv FilePath b) => a -> b
show Integer
actual
ResponseHeader "Content-Length" Integer
_ -> IO ()
forall (f :: * -> *). Applicative f => f ()
pass
pure (ByteString -> ByteString
BL.toStrict ByteString
bl)
case FilePath
file of
FilePath
"-" -> ByteString -> RIO (HerculesClientToken, HerculesClientEnv) ()
forall (m :: * -> *). MonadIO m => ByteString -> m ()
BS.putStr ByteString
bytes
FilePath
_ -> FilePath
-> ByteString -> RIO (HerculesClientToken, HerculesClientEnv) ()
forall (m :: * -> *). MonadIO m => FilePath -> ByteString -> m ()
BS.writeFile FilePath
file ByteString
bytes
putCommandParser :: Parser (IO ())
putCommandParser = do
Maybe ProjectPath
projectMaybe <- Parser ProjectPath -> Parser (Maybe ProjectPath)
forall (f :: * -> *) a. Alternative f => f a -> f (Maybe a)
optional Parser ProjectPath
projectOption
Text
name <- Parser Text
nameOption
FilePath
file <- Parser FilePath
fileOption
pure do
RIO (HerculesClientToken, HerculesClientEnv) () -> IO ()
forall b. RIO (HerculesClientToken, HerculesClientEnv) b -> IO b
runAuthenticated do
ProjectStateResourceGroup ClientAuth (AsClientT ClientM)
projectStateClient <- Maybe ProjectPath
-> RIO
(HerculesClientToken, HerculesClientEnv)
(ProjectStateResourceGroup ClientAuth (AsClientT ClientM))
forall r.
(Has HerculesClientToken r, Has HerculesClientEnv r) =>
Maybe ProjectPath
-> RIO r (ProjectStateResourceGroup ClientAuth (AsClientT ClientM))
getProjectAndClient Maybe ProjectPath
projectMaybe
ByteString
bytes <- case FilePath
file of
FilePath
"-" -> RIO (HerculesClientToken, HerculesClientEnv) ByteString
forall (m :: * -> *). MonadIO m => m ByteString
BS.getContents
FilePath
_ -> FilePath -> RIO (HerculesClientToken, HerculesClientEnv) ByteString
forall (m :: * -> *). MonadIO m => FilePath -> m ByteString
BS.readFile FilePath
file
NoContent
_ :: NoContent <- Text
-> (Token -> ClientM NoContent)
-> RIO (HerculesClientToken, HerculesClientEnv) NoContent
forall b r.
(NFData b, Has HerculesClientToken r, Has HerculesClientEnv r) =>
Text -> (Token -> ClientM b) -> RIO r b
retryOnFail Text
"state put" do
ProjectStateResourceGroup ClientAuth (AsClientT ClientM)
-> AsClientT ClientM
:- (Summary "Upload a state file"
:> ("state"
:> (Capture' '[Required, Strict] "stateName" Text
:> ("data"
:> (StreamBody NoFraming OctetStream (SourceIO RawBytes)
:> (ClientAuth :> Put '[JSON] NoContent))))))
forall auth f.
ProjectStateResourceGroup auth f
-> f
:- (Summary "Upload a state file"
:> ("state"
:> (Capture' '[Required, Strict] "stateName" Text
:> ("data"
:> (StreamBody NoFraming OctetStream (SourceIO RawBytes)
:> (auth :> Put '[JSON] NoContent))))))
putStateData ProjectStateResourceGroup ClientAuth (AsClientT ClientM)
projectStateClient Text
name ([RawBytes] -> SourceIO RawBytes
forall a (m :: * -> *). [a] -> SourceT m a
Servant.source [ByteString -> RawBytes
RawBytes ByteString
bytes])
Text -> RIO (HerculesClientToken, HerculesClientEnv) ()
forall (m :: * -> *). MonadIO m => Text -> m ()
putErrText (Text -> RIO (HerculesClientToken, HerculesClientEnv) ())
-> Text -> RIO (HerculesClientToken, HerculesClientEnv) ()
forall a b. (a -> b) -> a -> b
$ Text
"hci: State file upload successful for " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
name
nameOption :: Optparse.Parser Text
nameOption :: Parser Text
nameOption = Mod OptionFields Text -> Parser Text
forall s. IsString s => Mod OptionFields s -> Parser s
strOption (Mod OptionFields Text -> Parser Text)
-> Mod OptionFields Text -> Parser Text
forall a b. (a -> b) -> a -> b
$ FilePath -> Mod OptionFields Text
forall (f :: * -> *) a. HasName f => FilePath -> Mod f a
long FilePath
"name" Mod OptionFields Text
-> Mod OptionFields Text -> Mod OptionFields Text
forall a. Semigroup a => a -> a -> a
<> FilePath -> Mod OptionFields Text
forall (f :: * -> *) a. HasMetavar f => FilePath -> Mod f a
metavar FilePath
"NAME" Mod OptionFields Text
-> Mod OptionFields Text -> Mod OptionFields Text
forall a. Semigroup a => a -> a -> a
<> FilePath -> Mod OptionFields Text
forall (f :: * -> *) a. FilePath -> Mod f a
help FilePath
"Name of the state file"
fileOption :: Optparse.Parser FilePath
fileOption :: Parser FilePath
fileOption = Mod OptionFields FilePath -> Parser FilePath
forall s. IsString s => Mod OptionFields s -> Parser s
strOption (Mod OptionFields FilePath -> Parser FilePath)
-> Mod OptionFields FilePath -> Parser FilePath
forall a b. (a -> b) -> a -> b
$ FilePath -> Mod OptionFields FilePath
forall (f :: * -> *) a. HasName f => FilePath -> Mod f a
long FilePath
"file" Mod OptionFields FilePath
-> Mod OptionFields FilePath -> Mod OptionFields FilePath
forall a. Semigroup a => a -> a -> a
<> FilePath -> Mod OptionFields FilePath
forall (f :: * -> *) a. HasMetavar f => FilePath -> Mod f a
metavar FilePath
"FILE" Mod OptionFields FilePath
-> Mod OptionFields FilePath -> Mod OptionFields FilePath
forall a. Semigroup a => a -> a -> a
<> FilePath -> Mod OptionFields FilePath
forall (f :: * -> *) a. FilePath -> Mod f a
help FilePath
"Local path of the state file or - for stdio" Mod OptionFields FilePath
-> Mod OptionFields FilePath -> Mod OptionFields FilePath
forall a. Semigroup a => a -> a -> a
<> Completer -> Mod OptionFields FilePath
forall (f :: * -> *) a. HasCompleter f => Completer -> Mod f a
completer (FilePath -> Completer
bashCompleter FilePath
"file")
versionOption :: Optparse.Parser Int
versionOption :: Parser Int
versionOption = ReadM Int -> Mod OptionFields Int -> Parser Int
forall a. ReadM a -> Mod OptionFields a -> Parser a
option ReadM Int
forall a. Read a => ReadM a
auto (Mod OptionFields Int -> Parser Int)
-> Mod OptionFields Int -> Parser Int
forall a b. (a -> b) -> a -> b
$ FilePath -> Mod OptionFields Int
forall (f :: * -> *) a. HasName f => FilePath -> Mod f a
long FilePath
"version" Mod OptionFields Int
-> Mod OptionFields Int -> Mod OptionFields Int
forall a. Semigroup a => a -> a -> a
<> FilePath -> Mod OptionFields Int
forall (f :: * -> *) a. HasMetavar f => FilePath -> Mod f a
metavar FilePath
"INT" Mod OptionFields Int
-> Mod OptionFields Int -> Mod OptionFields Int
forall a. Semigroup a => a -> a -> a
<> FilePath -> Mod OptionFields Int
forall (f :: * -> *) a. FilePath -> Mod f a
help FilePath
"Version of the state file to retrieve"
getProjectAndClient :: (Has HerculesClientToken r, Has HerculesClientEnv r) => Maybe ProjectPath -> RIO r (ProjectStateResourceGroup ClientAuth (AsClientT ClientM))
getProjectAndClient :: forall r.
(Has HerculesClientToken r, Has HerculesClientEnv r) =>
Maybe ProjectPath
-> RIO r (ProjectStateResourceGroup ClientAuth (AsClientT ClientM))
getProjectAndClient Maybe ProjectPath
projectMaybe =
case Maybe ProjectPath
projectMaybe of
Just ProjectPath
projectPath ->
ProjectStateResourceGroup ClientAuth (AsClientT ClientM)
-> RIO r (ProjectStateResourceGroup ClientAuth (AsClientT ClientM))
forall a. a -> RIO r a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (StateAPI ClientAuth (AsClientT ClientM)
stateClient StateAPI ClientAuth (AsClientT ClientM)
-> (StateAPI ClientAuth (AsClientT ClientM)
-> ToServant
(ProjectStateResourceGroup ClientAuth) (AsClientT ClientM))
-> ProjectStateResourceGroup ClientAuth (AsClientT ClientM)
forall {k} (subapi :: k -> * -> *) (api :: k -> * -> *) mode
(a :: k).
GenericServant (subapi a) mode =>
api a mode
-> (api a mode -> ToServant (subapi a) mode) -> subapi a mode
`enterApiE` \StateAPI ClientAuth (AsClientT ClientM)
api -> StateAPI ClientAuth (AsClientT ClientM)
-> AsClientT ClientM
:- Substitute
("site"
:> (Capture' '[Required, Strict] "site" (Name Forge)
:> ("account"
:> (Capture' '[Required, Strict] "account" (Name Account)
:> ("project"
:> (Capture' '[Required, Strict] "project" (Name Project)
:> Placeholder))))))
(ToServantApi (ProjectStateResourceGroup ClientAuth))
forall auth f.
StateAPI auth f
-> f
:- Substitute
("site"
:> (Capture' '[Required, Strict] "site" (Name Forge)
:> ("account"
:> (Capture' '[Required, Strict] "account" (Name Account)
:> ("project"
:> (Capture' '[Required, Strict] "project" (Name Project)
:> Placeholder))))))
(ToServantApi (ProjectStateResourceGroup auth))
byProjectName StateAPI ClientAuth (AsClientT ClientM)
api (Text -> Name Forge
forall k (a :: k). Text -> Name a
Name (Text -> Name Forge) -> Text -> Name Forge
forall a b. (a -> b) -> a -> b
$ ProjectPath -> Text
projectPathSite ProjectPath
projectPath) (Text -> Name Account
forall k (a :: k). Text -> Name a
Name (Text -> Name Account) -> Text -> Name Account
forall a b. (a -> b) -> a -> b
$ ProjectPath -> Text
projectPathOwner ProjectPath
projectPath) (Text -> Name Project
forall k (a :: k). Text -> Name a
Name (Text -> Name Project) -> Text -> Name Project
forall a b. (a -> b) -> a -> b
$ ProjectPath -> Text
projectPathProject ProjectPath
projectPath))
Maybe ProjectPath
Nothing -> do
(Maybe (Id Project)
projectIdMaybe, ProjectPath
projectPath) <- RIO r (Maybe (Id Project), ProjectPath)
forall r.
(Has HerculesClientToken r, Has HerculesClientEnv r) =>
RIO r (Maybe (Id Project), ProjectPath)
findProjectContextually
case Maybe (Id Project)
projectIdMaybe of
Just Id Project
projectId ->
ProjectStateResourceGroup ClientAuth (AsClientT ClientM)
-> RIO r (ProjectStateResourceGroup ClientAuth (AsClientT ClientM))
forall a. a -> RIO r a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (StateAPI ClientAuth (AsClientT ClientM)
stateClient StateAPI ClientAuth (AsClientT ClientM)
-> (StateAPI ClientAuth (AsClientT ClientM)
-> ToServant
(ProjectStateResourceGroup ClientAuth) (AsClientT ClientM))
-> ProjectStateResourceGroup ClientAuth (AsClientT ClientM)
forall {k} (subapi :: k -> * -> *) (api :: k -> * -> *) mode
(a :: k).
GenericServant (subapi a) mode =>
api a mode
-> (api a mode -> ToServant (subapi a) mode) -> subapi a mode
`enterApiE` \StateAPI ClientAuth (AsClientT ClientM)
api -> StateAPI ClientAuth (AsClientT ClientM)
-> AsClientT ClientM
:- Substitute
("projects"
:> (Capture' '[Required, Strict] "projectId" (Id Project)
:> Placeholder))
(ToServantApi (ProjectStateResourceGroup ClientAuth))
forall auth f.
StateAPI auth f
-> f
:- Substitute
("projects"
:> (Capture' '[Required, Strict] "projectId" (Id Project)
:> Placeholder))
(ToServantApi (ProjectStateResourceGroup auth))
byProjectId StateAPI ClientAuth (AsClientT ClientM)
api Id Project
projectId)
Maybe (Id Project)
Nothing ->
ProjectStateResourceGroup ClientAuth (AsClientT ClientM)
-> RIO r (ProjectStateResourceGroup ClientAuth (AsClientT ClientM))
forall a. a -> RIO r a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (StateAPI ClientAuth (AsClientT ClientM)
stateClient StateAPI ClientAuth (AsClientT ClientM)
-> (StateAPI ClientAuth (AsClientT ClientM)
-> ToServant
(ProjectStateResourceGroup ClientAuth) (AsClientT ClientM))
-> ProjectStateResourceGroup ClientAuth (AsClientT ClientM)
forall {k} (subapi :: k -> * -> *) (api :: k -> * -> *) mode
(a :: k).
GenericServant (subapi a) mode =>
api a mode
-> (api a mode -> ToServant (subapi a) mode) -> subapi a mode
`enterApiE` \StateAPI ClientAuth (AsClientT ClientM)
api -> StateAPI ClientAuth (AsClientT ClientM)
-> AsClientT ClientM
:- Substitute
("site"
:> (Capture' '[Required, Strict] "site" (Name Forge)
:> ("account"
:> (Capture' '[Required, Strict] "account" (Name Account)
:> ("project"
:> (Capture' '[Required, Strict] "project" (Name Project)
:> Placeholder))))))
(ToServantApi (ProjectStateResourceGroup ClientAuth))
forall auth f.
StateAPI auth f
-> f
:- Substitute
("site"
:> (Capture' '[Required, Strict] "site" (Name Forge)
:> ("account"
:> (Capture' '[Required, Strict] "account" (Name Account)
:> ("project"
:> (Capture' '[Required, Strict] "project" (Name Project)
:> Placeholder))))))
(ToServantApi (ProjectStateResourceGroup auth))
byProjectName StateAPI ClientAuth (AsClientT ClientM)
api (Text -> Name Forge
forall k (a :: k). Text -> Name a
Name (Text -> Name Forge) -> Text -> Name Forge
forall a b. (a -> b) -> a -> b
$ ProjectPath -> Text
projectPathSite ProjectPath
projectPath) (Text -> Name Account
forall k (a :: k). Text -> Name a
Name (Text -> Name Account) -> Text -> Name Account
forall a b. (a -> b) -> a -> b
$ ProjectPath -> Text
projectPathOwner ProjectPath
projectPath) (Text -> Name Project
forall k (a :: k). Text -> Name a
Name (Text -> Name Project) -> Text -> Name Project
forall a b. (a -> b) -> a -> b
$ ProjectPath -> Text
projectPathProject ProjectPath
projectPath))