{- |
Module      :  Servant.Static.TH

Copyright   :  Dennis Gosnell 2017
License     :  BSD3

Maintainer  :  Dennis Gosnell (cdep.illabout@gmail.com)
Stability   :  experimental
Portability :  unknown

This module provides the 'createApiAndServerDecs' function.  At compile time,
it will read all the files under a specified directory, embed their contents,
create a Servant \"API\" type synonym representing their directory layout, and
create a 'ServerT' function for serving their contents statically.

Let's assume that we have a directory called @\"dir\"@ in the root of our
Haskell web API that looks like this:

@
  $ tree dir\/
  dir\/
  ├── js
  │   └── test.js
  ├── hello.html
  └── index.html
@

Here's the contents of @\"hello.html\"@, @\"index.html\"@, and @\"js\/test.js\"@:

@
  $ cat dir\/hello.html
  \<p\>Hello World\<\/p\>
  $ cat dir\/index.html
  \<p\>This is the index\<\/p\>
  $ cat dir\/js\/test.js
  console.log(\"hello world\");
@

The 'createApiAndServerDecs' function can be used like the following:

@
  \{\-\# LANGUAGE DataKinds \#\-\}
  \{\-\# LANGUAGE TemplateHaskell \#\-\}

  import "Data.Proxy" ('Data.Proxy.Proxy'('Data.Proxy.Proxy'))
  import "Network.Wai" ('Network.Wai.Application')
  import "Network.Wai.Handler.Warp" ('Network.Wai.Handler.Warp.run')
  import "Servant.Server" ('Servant.Server.serve')
  import "Servant.Static.TH" ('createApiAndServerDecs')

  $(createApiAndServerDecs \"FrontEndApi\" \"frontEndServer\" \"dir\")

  app :: 'Network.Wai.Application'
  app = 'Servant.Server.serve' ('Data.Proxy.Proxy' :: 'Data.Proxy.Proxy' FrontEndApi) frontEndServer

  main :: IO ()
  main = 'Network.Wai.Handler.Warp.run' 8080 app
@

'createApiAndServerDecs' will expand to something like the following at
compile time:

@
  type FrontEndAPI =
    -- index.html is served on the root, as well as from the path \"/index.html\".
         'Servant.API.Get' \'['HTML'] 'Html'
    ':<|>' \"index.html\" 'Servant.API.:>' 'Servant.API.Get' \'['HTML'] 'Html'
    -- hello.html is served from the path \"/hello.html\".
    ':<|>' \"hello.html\" 'Servant.API.:>' 'Servant.API.Get' \'['HTML'] 'Html'
    -- js/test.js is served from the path \"/js/test.js\".
    ':<|>' \"js\" 'Servant.API.:>' \"test.js\" 'Servant.API.:>' 'Servant.API.Get' \'['JS'] 'Data.ByteString.ByteString'

  frontEndServer :: 'Applicative' m => 'Servant.Server.ServerT' FrontEndAPI m
  frontEndServer =
         'pure' "\<p\>This is the index\<\/p\>"
    ':<|>' 'pure' "\<p\>This is the index\<\/p\>"
    ':<|>' 'pure' "\<p\>Hello World\<\/p\>"
    ':<|>' 'pure' "console.log(\\"hello world\\");"
@

If this WAI application is running, it is possible to use @curl@ to access
the server:

@
  $ curl localhost:8080\/
  \<p\>This is the index\<\/p\>
  $ curl localhost:8080\/index.html
  \<p\>This is the index\<\/p\>
  $ curl localhost:8080\/hello.html
  \<p\>Hello World\<\/p\>
  $ curl localhost:8080\/js\/test.js
  console.log(\"hello world\");
@

This 'createApiAndServerDecs' function is convenient to use when you want to
make a Servant application easy to deploy.  All the static frontend files are
bundled into the Haskell binary at compile-time, so all you need to do is
deploy the Haskell binary.  This works well for low-traffic websites like
prototypes and internal applications.

This shouldn't be used for high-traffic websites.  Instead, you should serve
your static files from something like Apache, nginx, or a CDN.

Note:

If you are creating a @cabal@ package that needs to work with @cabal-install@,
the @\"dir\"@ you want to serve needs to be a relative path inside your
project root, and all contained files need to be listed in your
@.cabal@-file under the @extra-source-files@ field so that they are
included and available at compile-time.
-}

module Servant.Static.TH
  ( -- * Create API
    createApiType
  , createApiDec
    -- * Create Server
  , createServerExp
  , createServerDec
    -- * Create Both API and Server
  , createApiAndServerDecs
    -- * MIME Types

    -- | The following types are the MIME types supported by servant-static-th.
    -- If you need additional MIME types supported, feel free to create an
    -- <https://github.com/cdepillabout/servant-static-th/issues issue> or
    -- <https://github.com/cdepillabout/servant-static-th/pulls PR>.
  , CSS
  , EOT
  , GEXF
  , GIF
  , HTML
  , Html
  , ICO
  , JPEG
  , JS
  , JSON
  , PNG
  , SVG
  , TTF
  , TXT
  , WOFF
  , WOFF2
  , XML
    -- * Easy-To-Use Names and Paths

    -- | The functions in this section pick defaults for the template
    -- directory, api name, and the server function name. This makes it easy to
    -- use for quick-and-dirty code.

    -- ** Paths and Names
  , frontEndTemplateDir
  , frontEndApiName
  , frontEndServerName
    -- ** API
  , createApiFrontEndType
  , createApiFrontEndDec
    -- ** Server
  , createServerFrontEndExp
  , createServerFrontEndDec
    -- ** Server and API
  , createApiAndServerFrontEndDecs
  ) where

import Language.Haskell.TH (Dec, Exp, Q, Type, mkName, tySynD)
import System.FilePath ((</>))
import Servant.HTML.Blaze (HTML)
import Text.Blaze.Html (Html)

import Servant.Static.TH.Internal
  ( CSS
  , EOT
  , GEXF
  , GIF
  , ICO
  , JPEG
  , JSON
  , JS
  , PNG
  , SVG
  , TTF
  , TXT
  , WOFF
  , WOFF2
  , XML
  , createApiDec
  , createApiType
  , createServerDec
  , createServerExp
  )

------------------------------------
-- Hard-coded Frontend file paths --
------------------------------------

-- | This is the directory @\"frontend\/dist\"@.
frontEndTemplateDir :: FilePath
frontEndTemplateDir :: FilePath
frontEndTemplateDir = FilePath
"frontend" FilePath -> FilePath -> FilePath
</> FilePath
"dist"

-- | This is the 'String' @\"FrontEnd\"@.
frontEndApiName :: String
frontEndApiName :: FilePath
frontEndApiName = FilePath
"FrontEnd"

-- | This is the 'String' @\"frontEndServer\"@.
frontEndServerName :: String
frontEndServerName :: FilePath
frontEndServerName = FilePath
"frontEndServer"

---------
-- Api --
---------

-- | This is the same as @'createApiType' 'frontEndTemplateDir'@.
createApiFrontEndType :: Q Type
createApiFrontEndType :: Q Type
createApiFrontEndType = FilePath -> Q Type
createApiType FilePath
frontEndTemplateDir

-- | This is the same as
-- @'createApiDec' 'frontEndApiName' 'frontEndTemplateDir'@.
createApiFrontEndDec :: Q [Dec]
createApiFrontEndDec :: Q [Dec]
createApiFrontEndDec =
  Dec -> [Dec]
forall a. a -> [a]
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Dec -> [Dec]) -> Q Dec -> Q [Dec]
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Name -> [TyVarBndr ()] -> Q Type -> Q Dec
forall (m :: * -> *).
Quote m =>
Name -> [TyVarBndr ()] -> m Type -> m Dec
tySynD (FilePath -> Name
mkName FilePath
"FrontEnd") [] Q Type
createApiFrontEndType

------------
-- Server --
------------

-- | This is the same as @'createServerExp' 'frontEndTemplateDir'@.
createServerFrontEndExp :: Q Exp
createServerFrontEndExp :: Q Exp
createServerFrontEndExp = FilePath -> Q Exp
createServerExp FilePath
frontEndTemplateDir

-- | This is the same as
-- @'createServerDec' 'frontEndApiName' 'frontEndServerName' 'frontEndTemplateDir'@.
createServerFrontEndDec :: Q [Dec]
createServerFrontEndDec :: Q [Dec]
createServerFrontEndDec =
  FilePath -> FilePath -> FilePath -> Q [Dec]
createServerDec FilePath
frontEndApiName FilePath
frontEndServerName FilePath
frontEndTemplateDir

--------------------
-- Server and API --
--------------------

-- | This is a combination of 'createApiDec' and 'createServerDec'.  This
-- function is the one most users should use.
--
-- Given the following code:
--
-- @
--   \{\-\# LANGUAGE DataKinds \#\-\}
--   \{\-\# LANGUAGE TemplateHaskell \#\-\}
--
--   $('createApiAndServerDecs' \"FrontAPI\" \"frontServer\" \"dir\")
-- @
--
-- You can think of it as expanding to the following:
--
-- @
--   $('createApiDec' \"FrontAPI\" \"dir\")
--
--   $('createServerDec' \"FrontAPI\" \"frontServer\" \"dir\")
-- @
createApiAndServerDecs
  :: String   -- ^ name of the api type synonym
  -> String   -- ^ name of the server function
  -> FilePath -- ^ directory name to read files from
  -> Q [Dec]
createApiAndServerDecs :: FilePath -> FilePath -> FilePath -> Q [Dec]
createApiAndServerDecs FilePath
apiName FilePath
serverName FilePath
templateDir =
  let apiDecs :: Q [Dec]
apiDecs = FilePath -> FilePath -> Q [Dec]
createApiDec FilePath
apiName FilePath
templateDir
      serverDecs :: Q [Dec]
serverDecs = FilePath -> FilePath -> FilePath -> Q [Dec]
createServerDec FilePath
apiName FilePath
serverName FilePath
templateDir
  in [Dec] -> [Dec] -> [Dec]
forall a. Monoid a => a -> a -> a
mappend ([Dec] -> [Dec] -> [Dec]) -> Q [Dec] -> Q ([Dec] -> [Dec])
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> Q [Dec]
apiDecs Q ([Dec] -> [Dec]) -> Q [Dec] -> Q [Dec]
forall a b. Q (a -> b) -> Q a -> Q b
forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Q [Dec]
serverDecs

-- | This is the same as
-- @'createApiAndServerDecs' 'frontEndApiName' 'frontEndServerName' 'frontEndTemplateDir'@.
createApiAndServerFrontEndDecs :: Q [Dec]
createApiAndServerFrontEndDecs :: Q [Dec]
createApiAndServerFrontEndDecs =
  FilePath -> FilePath -> FilePath -> Q [Dec]
createApiAndServerDecs FilePath
frontEndApiName FilePath
frontEndServerName FilePath
frontEndTemplateDir