{-# LANGUAGE DefaultSignatures #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE TypeInType #-} {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} -- | -- Module : Servant.CLI -- Copyright : (c) Justin Le 2019 -- License : BSD3 -- -- Maintainer : justin@jle.im -- Stability : experimental -- Portability : non-portable -- -- Parse command line arguments into a servant client, from a servant API. -- -- Mainly used through 'parseClient' and 'parseHandleClient'. -- 'parseClient' returns a servant client action that returns nested -- 'Either's for every endpoint, but 'parseHandleClient' allows you to -- conveniently specify how you want to sort each endpoint entry into -- a single result. -- -- See for -- a tutorial. module Servant.CLI ( -- * Parse Client parseClient, parseHandleClient -- ** With context , parseClientWithContext, parseHandleClientWithContext -- * Typeclasses , HasCLI (CLIResult, CLIHandler, cliHandler) -- * Context , ContextFor(..) , NamedContext(..) , descendIntoNamedContext -- * Lower-level , cliPStruct, cliPStructWithContext , structParser -- ** With context , cliHandlePStruct, cliHandlePStructWithContext -- * Re-export , ParseBody(..), defaultParseBody , ToCapture(..), DocCapture(..) , ToParam(..), DocQueryParam(..), ParamKind(..) , ToAuthInfo(..), DocAuthentication(..) ) where import Data.Proxy import Data.Vinyl import Options.Applicative import Servant.CLI.HasCLI import Servant.CLI.Internal.PStruct import Servant.CLI.ParseBody import Servant.Client.Core import Servant.Docs.Internal -- | A version of 'cliPStruct' that can be used if the API requires -- any external context to generate runtime data. cliPStructWithContext :: HasCLI m api context => Proxy m -- ^ Client monad -> Proxy api -- ^ API -> Rec (ContextFor m) context -- ^ Extra context -> PStruct (m (CLIResult m api)) cliPStructWithContext pm pa = fmap ($ defaultRequest) . cliPStructWithContext_ pm pa -- | A version of 'cliHandlePStruct' that can be used if the API requires -- any external context to generate runtime data. cliHandlePStructWithContext :: forall m api context r. (HasCLI m api context, Functor m) => Proxy m -- ^ Client monad -> Proxy api -- ^ API -> Rec (ContextFor m) context -- ^ Extra context -> CLIHandler m api r -- ^ Handler -> PStruct (m r) cliHandlePStructWithContext pm pa p h = fmap (cliHandler pm pa (Proxy @context) h) <$> cliPStructWithContext pm pa p -- | A version of 'parseClient' that can be used if the API requires -- any external context to generate runtime data. parseClientWithContext :: HasCLI m api context => Proxy api -- ^ API -> Proxy m -- ^ Client monad -> Rec (ContextFor m) context -- ^ Extra context -> InfoMod (m (CLIResult m api)) -- ^ Options for top-level display -> IO (m (CLIResult m api)) parseClientWithContext pa pm p im = execParser . flip structParser im $ cliPStructWithContext pm pa p -- | A version of 'parseHandleClient' that can be used if the API requires -- any external context to generate runtime data. parseHandleClientWithContext :: forall m api context r. (HasCLI m api context, Functor m) => Proxy api -- ^ API -> Proxy m -- ^ Client monad -> Rec (ContextFor m) context -- ^ Extra context -> InfoMod (m (CLIResult m api)) -- ^ Options for top-level display -> CLIHandler m api r -- ^ Handler -> IO (m r) parseHandleClientWithContext pa pm p im h = fmap (cliHandler pm pa (Proxy @context) h) <$> parseClientWithContext pa pm p im -- | Create a structure for a command line parser. -- -- This can be useful if you are combining functionality with existing -- /optparse-applicative/ parsers. You can convert a 'PStruct' to -- a 'Parser' using 'structParser'. cliPStruct :: HasCLI m api '[] => Proxy m -- ^ Client monad -> Proxy api -- ^ API -> PStruct (m (CLIResult m api)) cliPStruct pm pa = cliPStructWithContext pm pa RNil -- | Create a structure for a command line parser, producing results -- according to a 'CLIHandler'. See 'parseHandleClient' for more -- information. -- -- This can be useful if you are combining functionality with existing -- /optparse-applicative/ parsers. You can convert a 'PStruct' to -- a 'Parser' using 'structParser'. cliHandlePStruct :: (HasCLI m api '[], Functor m) => Proxy m -- ^ Client monad -> Proxy api -- ^ API -> CLIHandler m api r -- ^ Handler -> PStruct (m r) cliHandlePStruct pm pa = cliHandlePStructWithContext pm pa RNil -- | Parse a servant client; the result can be run. The choice of @m@ -- gives the backend you are using; for example, the default GHC -- /servant-client/ backend is 'Servant.Client.ClientM'. -- -- Returns the request response, which is usually a layer of 'Either' for -- every endpoint branch. You can find the response type directly by using -- typed holes or asking ghci with @:t@ or @:kind! forall m. CLIResult -- m MyAPI@. Because it might be tedious handling nested 'Either's, see -- 'parseHandleClient' for a way to handle each potential branch in -- a convenient way. -- -- Takes options on how the top-level prompt is displayed when given -- @"--help"@; it can be useful for adding a header or program description. -- Otherwise, just use 'mempty'. parseClient :: HasCLI m api '[] => Proxy api -- ^ API -> Proxy m -- ^ Client monad -> InfoMod (m (CLIResult m api)) -- ^ Options for top-level display -> IO (m (CLIResult m api)) parseClient pa pm = parseClientWithContext pa pm RNil -- | Parse a server client, like 'parseClient'. However, instead of that -- client action returning the request response, instead use a 'CLIHandler' -- to handle every potential request response. It essentially lets you -- specify how to sort each potential endpoint's response into a single -- output value. -- -- The handler is usually a 'Servant.API.:<|>' for every endpoint branch. -- You can find it by using typed holes or asking ghci with @:t@ or @:kind! -- forall m r. CLIHandler m MyAPI r@. -- -- Takes options on how the top-level prompt is displayed when given -- @"--help"@; it can be useful for adding a header or program description. -- Otherwise, just use 'mempty'. parseHandleClient :: (HasCLI m api '[], Functor m) => Proxy api -- ^ API -> Proxy m -- ^ Client monad -> InfoMod (m (CLIResult m api)) -- ^ Options for top-level display -> CLIHandler m api r -- ^ Handler -> IO (m r) parseHandleClient pa pm = parseHandleClientWithContext pa pm RNil