-- | Inclusion of files in source code via Template Haskell.
--
--   When distributing executables, sometimes it is required to attach some other resources in
--   files. Using 'includeFileInSource' you avoid this problem by including those files inside
--   the executable at compile time.
--
-- = Example
--
--   A quick example. I want to include a small image (@foo.png@) in the executable. I would do:
--
-- > {-# LANGUAGE TemplateHaskell #-}
-- >
-- > import Development.IncludeFile
-- >
-- > $(includeFileInSource "foo.png" "myImage")
-- >
--
--   This defines the @myImage@ value with type 'B.ByteString' and with the content of the file @foo.png@.
--
-- = Using 'includeFileInSource'
--
--   The following entities must be in scope when calling 'includeFileInSource':
--
-- * A @ByteString@ type.
-- * A @Word8@ type, instance of the 'Num' class (at least with the 'fromInteger' method implemented).
-- * A @pack :: [Word8] -> ByteString@ function.
--
--   This module re-export these for convenience, but you can use your own types (for example, using
--   /lazy/ bytestrings instead of /strict/ bytestrings).
--
-- = Using lazy bytestrings
--
--   To use lazy bytestrings, instead of importing this full module, import it like this:
--
-- > import Data.ByteString.Lazy (ByteString,pack)
-- > import Development.IncludeFile (includeFileInSource,Word8)
--
--   Needless to say, if you have already imported any of those entities, you don't have to do it again.
--
-- = Performance impact
--
--   Benchmarks confirm that it is /much/ faster to use an included bytestring than reading it from a file.
--
-- > benchmarking include-file
-- > time                 1.814 ns   (1.799 ns .. 1.826 ns)
-- >                      1.000 R²   (0.999 R² .. 1.000 R²)
-- > mean                 1.808 ns   (1.797 ns .. 1.819 ns)
-- > std dev              37.48 ps   (31.27 ps .. 46.78 ps)
-- > 
-- > benchmarking read-file
-- > time                 4.869 μs   (4.798 μs .. 4.938 μs)
-- >                      0.998 R²   (0.998 R² .. 0.999 R²)
-- > mean                 4.911 μs   (4.857 μs .. 4.968 μs)
-- > std dev              178.8 ns   (150.1 ns .. 212.5 ns)
--
-- = Large files
--
--   Do not use 'includeFileInSource' with large files. The limit between what /is/ and what is /not/ a large
--   file remains uncertain.
--
module Development.IncludeFile (
    -- * Including files
    includeFileInSource
    -- * Convenient re-exports
  , B.ByteString
  , Word8
  , B.pack
  ) where

import qualified Data.ByteString as B
import Data.Word (Word8)
import Language.Haskell.TH

-- | Define a value of type @ByteString@ (where @ByteString@ is whatever @ByteString@ type is in scope)
--   with the content of a file.
includeFileInSource :: FilePath -- ^ Path to file
                    -> String -- ^ Haskell value name
                    -> Q [Dec]
includeFileInSource fp n = do
  b <- runIO $ B.readFile fp
  let ws = B.unpack b
      wtype = ConT $ mkName "Word8"
      btype = ConT $ mkName "ByteString"
  return
    [ SigD (mkName n) btype
    , FunD (mkName n) [Clause [] (NormalB $
         AppE (SigE (VarE $ mkName "pack") $ ArrowT `AppT` (ListT `AppT` wtype) `AppT` btype)
           $ ListE $ fmap (LitE . IntegerL . fromIntegral) ws
                                   ) []]
      ]