xstatic-th (empty) → 0.1.0
raw patch · 6 files changed
+223/−0 lines, 6 filesdep +SHAdep +basedep +bytestring
Dependencies added: SHA, base, bytestring, mime-types, tasty, tasty-hunit, template-haskell, text, xstatic, xstatic-th, zlib
Files
- CHANGELOG.md +5/−0
- LICENSE +29/−0
- data/test.js +1/−0
- src/XStatic/TH.hs +92/−0
- test/Test.hs +34/−0
- xstatic-th.cabal +62/−0
+ CHANGELOG.md view
@@ -0,0 +1,5 @@+# Changelog++## 0.1.0++- Initial release
+ LICENSE view
@@ -0,0 +1,29 @@+BSD 3-Clause License++Copyright (c) 2022, Tristan de Cacqueray+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++1. Redistributions of source code must retain the above copyright notice, this+ list of conditions and the following disclaimer.++2. Redistributions in binary form must reproduce the above copyright notice,+ this list of conditions and the following disclaimer in the documentation+ and/or other materials provided with the distribution.++3. Neither the name of the copyright holder nor the names of its+ contributors may be used to endorse or promote products derived from+ this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ data/test.js view
@@ -0,0 +1,1 @@+true
+ src/XStatic/TH.hs view
@@ -0,0 +1,92 @@+{-# LANGUAGE DeriveLift #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE GeneralisedNewtypeDeriving #-}+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE TemplateHaskell #-}++{- | Create a 'XStaticFile' served as /\/my-file.txt/ or /\/xstatic\/my-file.txt/:++@+myFile :: XStaticFile+myFile = $(embedXStaticFile ".\/data\/my-file.txt")+@+-}+module XStatic.TH (+ embedXStaticFile,+ embedXStaticFileVersion,++ -- * re-export from "XStatic"+ XStaticFile,+) where++import Codec.Compression.GZip qualified as GZip+import Data.ByteString (ByteString)+import Data.ByteString qualified as BS+import Data.ByteString.Char8 (pack)+import Data.ByteString.Char8 qualified as BS8+import Data.Digest.Pure.SHA qualified as SHA+import Data.List qualified+import Data.Maybe (fromMaybe)+import Data.Text qualified as Text+import Data.Version (Version (..), showVersion)+import Language.Haskell.TH (Exp (AppE))+import Language.Haskell.TH qualified as TH+import Language.Haskell.TH.Syntax qualified as TH+import Network.Mime qualified as Mime+import XStatic (XStaticFile (..), isGzip)++doEmbedXStaticFile ::+ FilePath ->+ Maybe ByteString ->+ TH.Q TH.Exp+doEmbedXStaticFile fp etag = do+ buf <- TH.runIO (BS.readFile fp)+ contentE <- TH.lift (ensureCompress buf)+ nameE <- TH.lift clean_name+ mimeE <- TH.lift (Mime.defaultMimeLookup (Text.pack fp_without_gz))+ etagE <- TH.lift (fromMaybe (hashBuf buf) etag)++ mkXF <- [|XStaticFile|]+ pure $!+ mkXF+ `AppE` nameE+ `AppE` contentE+ `AppE` etagE+ `AppE` mimeE+ where+ hashBuf = pack . SHA.showDigest . SHA.sha1 . BS.fromStrict+ ensureCompress buf+ | isGzip buf = buf+ | otherwise = BS.toStrict (GZip.compress (BS.fromStrict buf))++ fp_without_gz = stripSuffix ".gz" fp+ clean_name =+ pack+ . mappend "/"+ . stripSuffix "index.html"+ . stripPrefix "data/"+ . dropWhile (\c -> c == '.' || c == '/')+ $ fp_without_gz+ stripSuffix :: String -> String -> String+ stripSuffix s n = reverse $ stripPrefix (reverse s) (reverse n)+ stripPrefix :: String -> String -> String+ stripPrefix p n = fromMaybe n (Data.List.stripPrefix p n)++{- | Embed a static file in its compressed form.++The following rules are applied to convert a local filepath to the expected request path:++ * /./, /\// and \"/data\//\" prefix are removed+ * \"/.gz/\" and \"/index.html/\" suffix are removed+-}+embedXStaticFile :: FilePath -> TH.Q TH.Exp+embedXStaticFile fp = doEmbedXStaticFile fp Nothing++-- | Same as 'embedXStaticFile', but using the provided 'Version' for the 'xfETag' value.+embedXStaticFileVersion :: FilePath -> Version -> TH.Q TH.Exp+embedXStaticFileVersion fp version = doEmbedXStaticFile fp (Just $ versionToEtag version)++versionToEtag :: Version -> ByteString+versionToEtag = mconcat . BS8.split '.' . pack . showVersion
+ test/Test.hs view
@@ -0,0 +1,34 @@+{-# LANGUAGE ImportQualifiedPost #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}++module Main (main) where++import Codec.Compression.GZip qualified as GZip+import Data.ByteString qualified as BS+import Test.Tasty+import Test.Tasty.HUnit++import Paths_xstatic_th (version)+import XStatic+import XStatic.TH++main :: IO ()+main = defaultMain $ do+ testGroup+ "xstatic-th"+ [ testCase "Test embedXStaticFileVersion" $ do+ let xf :: XStaticFile+ xf = $(embedXStaticFileVersion "./xstatic-th.cabal" version)++ xfPath xf @?= "/xstatic-th.cabal"+ isGzip (xfContent xf) @? "Uncompressed file"+ xfType xf @?= "application/octet-stream"+ , testCase "Test embedXStaticFile" $ do+ let xf :: XStaticFile+ xf = $(embedXStaticFile "./data/test.js")+ xfPath xf @?= "/test.js"+ xfETag xf @?= "724ba28f4a9a1b472057ff99511ed393a45552e1"+ xfType xf @?= "application/javascript"+ xfContent xf @?= BS.toStrict (GZip.compress "true\n")+ ]
+ xstatic-th.cabal view
@@ -0,0 +1,62 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.34.7.+--+-- see: https://github.com/sol/hpack++name: xstatic-th+version: 0.1.0+synopsis: Automatic generation of XStaticFile+description: Automatic generation of XStaticFile.+category: JavaScript+homepage: https://github.com/TristanCacqueray/haskell-xstatic#readme+bug-reports: https://github.com/TristanCacqueray/haskell-xstatic/issues+author: Tristan Cacqueray+maintainer: tdecacqu@redhat.com+license: BSD3+license-file: LICENSE+build-type: Simple+extra-source-files:+ CHANGELOG.md+ data/test.js++source-repository head+ type: git+ location: https://github.com/TristanCacqueray/haskell-xstatic++library+ exposed-modules:+ XStatic.TH+ other-modules:+ Paths_xstatic_th+ hs-source-dirs:+ src/+ ghc-options: -Weverything -Wno-implicit-prelude -Wno-missing-kind-signatures -Wno-missing-import-lists -Wno-missing-safe-haskell-mode -Wno-unsafe+ build-depends:+ SHA+ , base <5+ , bytestring >=0.11.2+ , mime-types+ , template-haskell+ , text+ , xstatic+ , zlib+ default-language: Haskell2010++test-suite spec+ type: exitcode-stdio-1.0+ main-is: Test.hs+ other-modules:+ Paths_xstatic_th+ hs-source-dirs:+ test+ ghc-options: -Weverything -Wno-implicit-prelude -Wno-missing-kind-signatures -Wno-missing-import-lists -Wno-missing-safe-haskell-mode -Wno-unsafe+ build-depends:+ base <5+ , bytestring+ , tasty+ , tasty-hunit+ , xstatic+ , xstatic-th+ , zlib+ default-language: Haskell2010