static: add several embedded generators
This constains the generators to embed files, directories, and javascript compression
This commit is contained in:
parent
f8a35ce0a0
commit
2ad3977712
@ -6,6 +6,21 @@
|
|||||||
module Yesod.EmbeddedStatic.Generators (
|
module Yesod.EmbeddedStatic.Generators (
|
||||||
-- * Generators
|
-- * Generators
|
||||||
Location
|
Location
|
||||||
|
, embedFile
|
||||||
|
, embedFileAt
|
||||||
|
, embedDir
|
||||||
|
, embedDirAt
|
||||||
|
, concatFiles
|
||||||
|
, concatFilesWith
|
||||||
|
|
||||||
|
-- * Compression options for 'concatFilesWith'
|
||||||
|
, jasmine
|
||||||
|
, uglifyJs
|
||||||
|
, yuiJavascript
|
||||||
|
, yuiCSS
|
||||||
|
, closureJs
|
||||||
|
, compressTool
|
||||||
|
, tryCompressTools
|
||||||
|
|
||||||
-- * Util
|
-- * Util
|
||||||
, pathToName
|
, pathToName
|
||||||
@ -15,11 +30,187 @@ module Yesod.EmbeddedStatic.Generators (
|
|||||||
-- $example
|
-- $example
|
||||||
) where
|
) where
|
||||||
|
|
||||||
|
import Control.Applicative ((<$>))
|
||||||
|
import Control.Exception (try, SomeException)
|
||||||
|
import Control.Monad (forM)
|
||||||
|
import Control.Monad.Trans.Resource (runResourceT)
|
||||||
import Data.Char (isDigit, isLower)
|
import Data.Char (isDigit, isLower)
|
||||||
|
import Data.Conduit (($$), (=$))
|
||||||
|
import Data.Conduit.Process (proc, conduitProcess)
|
||||||
import Language.Haskell.TH
|
import Language.Haskell.TH
|
||||||
|
import Network.Mime (defaultMimeLookup)
|
||||||
|
import System.Directory (doesDirectoryExist, getDirectoryContents)
|
||||||
|
import System.FilePath ((</>))
|
||||||
|
import Text.Jasmine (minifym)
|
||||||
|
import qualified Data.ByteString.Lazy as BL
|
||||||
|
import qualified Data.Conduit.List as C
|
||||||
|
import qualified Data.Text as T
|
||||||
|
|
||||||
import Yesod.EmbeddedStatic.Types
|
import Yesod.EmbeddedStatic.Types
|
||||||
|
|
||||||
|
-- | Embed a single file. Equivalent to passing the same string twice to 'embedFileAt'.
|
||||||
|
embedFile :: FilePath -> Generator
|
||||||
|
embedFile f = embedFileAt f f
|
||||||
|
|
||||||
|
-- | Embed a single file at a given location within the static subsite and generate a
|
||||||
|
-- route variable based on the location via 'pathToName'. The @FilePath@ must be a relative
|
||||||
|
-- path to the directory in which you run @cabal build@. During development, the file located
|
||||||
|
-- at this filepath will be reloaded on every request. When compiling for production, the contents
|
||||||
|
-- of the file will be embedded into the executable and so the file does not need to be
|
||||||
|
-- distributed along with the executable.
|
||||||
|
embedFileAt :: Location -> FilePath -> Generator
|
||||||
|
embedFileAt loc f = do
|
||||||
|
let mime = defaultMimeLookup $ T.pack f
|
||||||
|
let entry = Entry {
|
||||||
|
ebHaskellName = Just $ pathToName loc
|
||||||
|
, ebLocation = loc
|
||||||
|
, ebMimeType = mime
|
||||||
|
, ebProductionContent = BL.readFile f
|
||||||
|
, ebDevelReload = [| BL.readFile $(litE $ stringL f) |]
|
||||||
|
, ebDevelExtraFiles = Nothing
|
||||||
|
}
|
||||||
|
return [entry]
|
||||||
|
|
||||||
|
-- | List all files recursively in a directory
|
||||||
|
getRecursiveContents :: Location -- ^ The directory to search
|
||||||
|
-> FilePath -- ^ The prefix to add to the filenames
|
||||||
|
-> IO [(Location,FilePath)]
|
||||||
|
getRecursiveContents prefix topdir = do
|
||||||
|
names <- getDirectoryContents topdir
|
||||||
|
let properNames = filter (`notElem` [".", ".."]) names
|
||||||
|
paths <- forM properNames $ \name -> do
|
||||||
|
let path = topdir </> name
|
||||||
|
let loc = if null prefix then name else prefix ++ "/" ++ name
|
||||||
|
isDirectory <- doesDirectoryExist path
|
||||||
|
if isDirectory
|
||||||
|
then getRecursiveContents loc path
|
||||||
|
else return [(loc, path)]
|
||||||
|
return (concat paths)
|
||||||
|
|
||||||
|
-- | Embed all files in a directory into the static subsite.
|
||||||
|
--
|
||||||
|
-- Equivalent to passing the empty string as the location to 'embedDirAt',
|
||||||
|
-- so the directory path itself is not part of the resource locations (and so
|
||||||
|
-- also not part of the generated route variable names).
|
||||||
|
embedDir :: FilePath -> Generator
|
||||||
|
embedDir = embedDirAt ""
|
||||||
|
|
||||||
|
-- | Embed all files in a directory to a given location within the static subsite.
|
||||||
|
--
|
||||||
|
-- The directory tree rooted at the 'FilePath' (which must be relative to the directory in
|
||||||
|
-- which you run @cabal build@) is embedded into the static subsite at the given
|
||||||
|
-- location. Also, route variables will be created based on the final location
|
||||||
|
-- of each file. For example, if a directory \"static\" contains the files
|
||||||
|
--
|
||||||
|
-- * css/bootstrap.css
|
||||||
|
--
|
||||||
|
-- * js/jquery.js
|
||||||
|
--
|
||||||
|
-- * js/bootstrap.js
|
||||||
|
--
|
||||||
|
-- then @embedDirAt \"somefolder\" \"static\"@ will
|
||||||
|
--
|
||||||
|
-- * Make the file @static\/css\/bootstrap.css@ available at the location
|
||||||
|
-- @somefolder\/css\/bootstrap.css@ within the static subsite and similarly
|
||||||
|
-- for the other two files.
|
||||||
|
--
|
||||||
|
-- * Create variables @somefolder_css_bootstrap_css@, @somefolder_js_jquery_js@,
|
||||||
|
-- @somefolder_js_bootstrap_js@ all of type @Route EmbeddedStatic@.
|
||||||
|
--
|
||||||
|
-- * During development, the files will be reloaded on every request. During
|
||||||
|
-- production, the contents of all files will be embedded into the executable.
|
||||||
|
--
|
||||||
|
-- * During development, files that are added to the directory while the server
|
||||||
|
-- is running will not be detected. You need to recompile the module which
|
||||||
|
-- contains the call to @mkEmbeddedStatic@. This will also generate new route
|
||||||
|
-- variables for the new files.
|
||||||
|
embedDirAt :: Location -> FilePath -> Generator
|
||||||
|
embedDirAt loc dir = do
|
||||||
|
files <- runIO $ getRecursiveContents loc dir
|
||||||
|
concat <$> mapM (uncurry embedFileAt) files
|
||||||
|
|
||||||
|
-- | Concatinate a list of files and embed it at the location. Equivalent to passing @return@ to
|
||||||
|
-- 'concatFilesWith'.
|
||||||
|
concatFiles :: Location -> [FilePath] -> Generator
|
||||||
|
concatFiles loc files = concatFilesWith loc return files
|
||||||
|
|
||||||
|
-- | Concatinate a list of files into a single 'BL.ByteString', run the resulting content through the given
|
||||||
|
-- function, embed it at the given location, and create a haskell variable name for the route based on
|
||||||
|
-- the location.
|
||||||
|
--
|
||||||
|
-- The processing function is only run when compiling for production, and the processing function is
|
||||||
|
-- executed at compile time. During development, on every request the files listed are reloaded,
|
||||||
|
-- concatenated, and served as a single resource at the given location without being processed.
|
||||||
|
concatFilesWith :: Location -> (BL.ByteString -> IO BL.ByteString) -> [FilePath] -> Generator
|
||||||
|
concatFilesWith loc process files = do
|
||||||
|
let load = do putStrLn $ "Creating " ++ loc
|
||||||
|
BL.concat <$> mapM BL.readFile files >>= process
|
||||||
|
expFiles = listE $ map (litE . stringL) files
|
||||||
|
expCt = [| BL.concat <$> mapM BL.readFile $expFiles |]
|
||||||
|
mime = defaultMimeLookup $ T.pack loc
|
||||||
|
return [Entry (Just $ pathToName loc) loc mime load expCt Nothing]
|
||||||
|
|
||||||
|
-- | Convienient rexport of 'minifym' with a type signature to work with 'concatFilesWith'.
|
||||||
|
jasmine :: BL.ByteString -> IO BL.ByteString
|
||||||
|
jasmine ct = return $ either (const ct) id $ minifym ct
|
||||||
|
|
||||||
|
-- | Use <https://github.com/mishoo/UglifyJS2 UglifyJS2> to compress javascript.
|
||||||
|
-- Assumes @uglifyjs@ is located in the path and uses options @[\"-m\", \"-c\"]@
|
||||||
|
-- to both mangle and compress and the option \"-\" to cause uglifyjs to read from
|
||||||
|
-- standard input.
|
||||||
|
uglifyJs :: BL.ByteString -> IO BL.ByteString
|
||||||
|
uglifyJs = compressTool "uglifyjs" ["-m", "-c", "-"]
|
||||||
|
|
||||||
|
-- | Use <http://yui.github.io/yuicompressor/ YUI Compressor> to compress javascript.
|
||||||
|
-- Assumes a script @yuicompressor@ is located in the path. If not, you can still
|
||||||
|
-- use something like
|
||||||
|
--
|
||||||
|
-- > compressTool "java" ["-jar", "/path/to/yuicompressor.jar", "--type", "js"]
|
||||||
|
yuiJavascript :: BL.ByteString -> IO BL.ByteString
|
||||||
|
yuiJavascript = compressTool "yuicompressor" ["--type", "js"]
|
||||||
|
|
||||||
|
-- | Use <http://yui.github.io/yuicompressor/ YUI Compressor> to compress CSS.
|
||||||
|
-- Assumes a script @yuicompressor@ is located in the path.
|
||||||
|
yuiCSS :: BL.ByteString -> IO BL.ByteString
|
||||||
|
yuiCSS = compressTool "yuicompressor" ["--type", "css"]
|
||||||
|
|
||||||
|
-- | Use <https://developers.google.com/closure/compiler/ Closure> to compress
|
||||||
|
-- javascript using the default options. Assumes a script @closure@ is located in
|
||||||
|
-- the path. If not, you can still run using
|
||||||
|
--
|
||||||
|
-- > compressTool "java" ["-jar", "/path/to/compiler.jar"]
|
||||||
|
closureJs :: BL.ByteString -> IO BL.ByteString
|
||||||
|
closureJs = compressTool "closure" []
|
||||||
|
|
||||||
|
-- | Helper to convert a process into a compression function. The process
|
||||||
|
-- should be set up to take input from standard input and write to standard output.
|
||||||
|
compressTool :: FilePath -- ^ program
|
||||||
|
-> [String] -- ^ options
|
||||||
|
-> BL.ByteString -> IO BL.ByteString
|
||||||
|
compressTool f opts ct = do
|
||||||
|
let src = C.sourceList $ BL.toChunks ct
|
||||||
|
p = proc f opts
|
||||||
|
sink = C.consume
|
||||||
|
compressed <- runResourceT (src $$ conduitProcess p =$ sink)
|
||||||
|
return $ BL.fromChunks compressed
|
||||||
|
|
||||||
|
|
||||||
|
-- | Try a list of processing functions (like the compressions above) one by one until
|
||||||
|
-- one succeeds (does not raise an exception). Once a processing function succeeds,
|
||||||
|
-- none of the remaining functions are used. If none succeeds, the input is just
|
||||||
|
-- returned unprocessed. This is helpful if you are distributing
|
||||||
|
-- code on hackage and do not know what compressors the user will have installed. You
|
||||||
|
-- can list several and they will be tried in order until one succeeds.
|
||||||
|
tryCompressTools :: [BL.ByteString -> IO BL.ByteString] -> BL.ByteString -> IO BL.ByteString
|
||||||
|
tryCompressTools [] x = return x
|
||||||
|
tryCompressTools (p:ps) x = do
|
||||||
|
mres <- try $ p x
|
||||||
|
case mres of
|
||||||
|
Left (err :: SomeException) -> do
|
||||||
|
putStrLn $ show err
|
||||||
|
tryCompressTools ps x
|
||||||
|
Right res -> return res
|
||||||
|
|
||||||
-- | Clean up a path to make it a valid haskell name by replacing all non-letters
|
-- | Clean up a path to make it a valid haskell name by replacing all non-letters
|
||||||
-- and non-numbers by underscores. In addition, if the path starts with a capital
|
-- and non-numbers by underscores. In addition, if the path starts with a capital
|
||||||
-- letter or number add an initial underscore.
|
-- letter or number add an initial underscore.
|
||||||
|
|||||||
92
yesod-static/test/FileGeneratorTests.hs
Normal file
92
yesod-static/test/FileGeneratorTests.hs
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
{-# LANGUAGE TemplateHaskell, QuasiQuotes, OverloadedStrings #-}
|
||||||
|
module FileGeneratorTests (fileGenSpecs) where
|
||||||
|
|
||||||
|
import Control.Exception
|
||||||
|
import Control.Monad (forM_)
|
||||||
|
import GeneratorTestUtil
|
||||||
|
import Test.Hspec
|
||||||
|
import Test.HUnit (assertFailure, assertEqual)
|
||||||
|
import Yesod.EmbeddedStatic.Generators
|
||||||
|
import qualified Data.ByteString.Lazy as BL
|
||||||
|
|
||||||
|
-- | Embeds the LICENSE file
|
||||||
|
license :: GenTestResult
|
||||||
|
license = $(embedFile "LICENSE" >>=
|
||||||
|
testOneEntry (Just "_LICENSE") "LICENSE" (BL.readFile "LICENSE")
|
||||||
|
)
|
||||||
|
|
||||||
|
licenseAt :: GenTestResult
|
||||||
|
licenseAt = $(embedFileAt "abc.txt" "LICENSE" >>=
|
||||||
|
testOneEntry (Just "abc_txt") "abc.txt" (BL.readFile "LICENSE")
|
||||||
|
)
|
||||||
|
|
||||||
|
embDir :: [GenTestResult]
|
||||||
|
embDir = $(embedDir "test/embed-dir" >>=
|
||||||
|
testEntries
|
||||||
|
[ (Just "abc_def_txt", "abc/def.txt", BL.readFile "test/embed-dir/abc/def.txt")
|
||||||
|
, (Just "lorem_txt", "lorem.txt", BL.readFile "test/embed-dir/lorem.txt")
|
||||||
|
, (Just "foo", "foo", BL.readFile "test/embed-dir/foo")
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
embDirAt :: [GenTestResult]
|
||||||
|
embDirAt = $(embedDirAt "xxx" "test/embed-dir" >>=
|
||||||
|
testEntries
|
||||||
|
[ (Just "xxx_abc_def_txt", "xxx/abc/def.txt", BL.readFile "test/embed-dir/abc/def.txt")
|
||||||
|
, (Just "xxx_lorem_txt", "xxx/lorem.txt", BL.readFile "test/embed-dir/lorem.txt")
|
||||||
|
, (Just "xxx_foo", "xxx/foo", BL.readFile "test/embed-dir/foo")
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
concatR :: GenTestResult
|
||||||
|
concatR = $(concatFiles "out.txt" [ "test/embed-dir/abc/def.txt", "test/embed-dir/foo"] >>=
|
||||||
|
testOneEntry (Just "out_txt") "out.txt" (return "Yesod Rocks\nBar\n")
|
||||||
|
)
|
||||||
|
|
||||||
|
-- The transform function should only run at compile for the production content
|
||||||
|
concatWithR :: GenTestResult
|
||||||
|
concatWithR = $(concatFilesWith "out2.txt"
|
||||||
|
(\x -> return $ x `BL.append` "Extra")
|
||||||
|
[ "test/embed-dir/abc/def.txt", "test/embed-dir/foo"] >>=
|
||||||
|
testOneEntry (Just "out2_txt") "out2.txt" (return "Yesod Rocks\nBar\nExtra")
|
||||||
|
)
|
||||||
|
|
||||||
|
fileGenSpecs :: Spec
|
||||||
|
fileGenSpecs = do
|
||||||
|
describe "Embed File" $ do
|
||||||
|
it "embeds a single file" $
|
||||||
|
assertGenResult (BL.readFile "LICENSE") license
|
||||||
|
it "embeds a single file at a location" $
|
||||||
|
assertGenResult (BL.readFile "LICENSE") licenseAt
|
||||||
|
|
||||||
|
describe "Embed Directory" $ do
|
||||||
|
it "embeds a directory" $
|
||||||
|
forM_ [embDir, embDirAt] $ \d -> case d of
|
||||||
|
[GenError e] -> assertFailure e
|
||||||
|
[def, foo, lorem] -> do
|
||||||
|
assertGenResult (BL.readFile "test/embed-dir/abc/def.txt") def
|
||||||
|
assertGenResult (BL.readFile "test/embed-dir/foo") foo
|
||||||
|
assertGenResult (BL.readFile "test/embed-dir/lorem.txt") lorem
|
||||||
|
_ -> assertFailure "Bad directory list"
|
||||||
|
|
||||||
|
describe "Concat Files" $ do
|
||||||
|
it "simple concat" $
|
||||||
|
assertGenResult (return "Yesod Rocks\nBar\n") concatR
|
||||||
|
it "concat with processing function" $
|
||||||
|
assertGenResult (return "Yesod Rocks\nBar\n") concatWithR -- no Extra since this is development
|
||||||
|
|
||||||
|
describe "Compress" $ do
|
||||||
|
it "compress tool function" $ do
|
||||||
|
out <- compressTool "runhaskell" [] "main = putStrLn \"Hello World\""
|
||||||
|
assertEqual "" "Hello World\n" out
|
||||||
|
|
||||||
|
it "tryCompressTools" $ do
|
||||||
|
out <- flip tryCompressTools "abcdef"
|
||||||
|
[ const $ throwIO $ ErrorCall "An expected error"
|
||||||
|
, const $ return "foo"
|
||||||
|
, const $ return "bar"
|
||||||
|
]
|
||||||
|
assertEqual "" "foo" out
|
||||||
|
out2 <- flip tryCompressTools "abcdef"
|
||||||
|
[ const $ throwIO $ ErrorCall "An expected error"]
|
||||||
|
assertEqual "" "abcdef" out2
|
||||||
59
yesod-static/test/GeneratorTestUtil.hs
Normal file
59
yesod-static/test/GeneratorTestUtil.hs
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
{-# LANGUAGE TemplateHaskell, QuasiQuotes, OverloadedStrings #-}
|
||||||
|
module GeneratorTestUtil where
|
||||||
|
|
||||||
|
import Control.Applicative
|
||||||
|
import Control.Monad (when)
|
||||||
|
import Data.List (sortBy)
|
||||||
|
import Language.Haskell.TH
|
||||||
|
import Test.HUnit
|
||||||
|
import Yesod.EmbeddedStatic.Types
|
||||||
|
import qualified Data.ByteString.Lazy as BL
|
||||||
|
|
||||||
|
-- We test the generators by executing them at compile time
|
||||||
|
-- and sticking the result into the GenTestResult. We then
|
||||||
|
-- test the GenTestResult at runtime. But to test the ebDevelReload
|
||||||
|
-- we must run the action at runtime so that is also embedded.
|
||||||
|
-- Because of template haskell stage restrictions, this code
|
||||||
|
-- needs to be in a separate module.
|
||||||
|
|
||||||
|
data GenTestResult = GenError String
|
||||||
|
| GenSuccessWithDevel (IO BL.ByteString)
|
||||||
|
|
||||||
|
-- | Creates a GenTestResult at compile time by testing the entry.
|
||||||
|
testEntry :: Maybe String -> Location -> IO BL.ByteString -> Entry -> ExpQ
|
||||||
|
testEntry name _ _ e | ebHaskellName e /= (mkName <$> name) =
|
||||||
|
[| GenError ("haskell name " ++ $(litE $ stringL $ show $ ebHaskellName e)
|
||||||
|
++ " /= "
|
||||||
|
++ $(litE $ stringL $ show name)) |]
|
||||||
|
testEntry _ loc _ e | ebLocation e /= loc =
|
||||||
|
[| GenError ("location " ++ $(litE $ stringL $ show $ ebLocation e)) |]
|
||||||
|
testEntry _ _ act e = do
|
||||||
|
expected <- runIO act
|
||||||
|
actual <- runIO $ ebProductionContent e
|
||||||
|
if expected == actual
|
||||||
|
then [| GenSuccessWithDevel $(ebDevelReload e) |]
|
||||||
|
else [| GenError "production content" |]
|
||||||
|
|
||||||
|
testOneEntry :: Maybe String -> Location -> IO BL.ByteString -> [Entry] -> ExpQ
|
||||||
|
testOneEntry name loc ct [e] = testEntry name loc ct e
|
||||||
|
testOneEntry _ _ _ _ = [| GenError "not exactly one entry" |]
|
||||||
|
|
||||||
|
-- | Tests a list of entries
|
||||||
|
testEntries :: [(Maybe String, Location, IO BL.ByteString)] -> [Entry] -> ExpQ
|
||||||
|
testEntries a b | length a /= length b = [| [GenError "lengths differ"] |]
|
||||||
|
testEntries a b = listE $ zipWith f a' b'
|
||||||
|
where
|
||||||
|
a' = sortBy (\(_,l1,_) (_,l2,_) -> compare l1 l2) a
|
||||||
|
b' = sortBy (\e1 e2 -> ebLocation e1 `compare` ebLocation e2) b
|
||||||
|
f (name, loc, ct) e = testEntry name loc ct e
|
||||||
|
|
||||||
|
-- | Use this at runtime to assert the 'GenTestResult' is OK
|
||||||
|
assertGenResult :: (IO BL.ByteString) -- ^ expected development content
|
||||||
|
-> GenTestResult -- ^ test result created at compile time
|
||||||
|
-> Assertion
|
||||||
|
assertGenResult _ (GenError e) = assertFailure ("invalid " ++ e)
|
||||||
|
assertGenResult mexpected (GenSuccessWithDevel mactual) = do
|
||||||
|
expected <- mexpected
|
||||||
|
actual <- mactual
|
||||||
|
when (expected /= actual) $
|
||||||
|
assertFailure "invalid devel content"
|
||||||
1
yesod-static/test/embed-dir/abc/def.txt
Normal file
1
yesod-static/test/embed-dir/abc/def.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
Yesod Rocks
|
||||||
1
yesod-static/test/embed-dir/foo
Normal file
1
yesod-static/test/embed-dir/foo
Normal file
@ -0,0 +1 @@
|
|||||||
|
Bar
|
||||||
6
yesod-static/test/embed-dir/lorem.txt
Normal file
6
yesod-static/test/embed-dir/lorem.txt
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor
|
||||||
|
incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
|
||||||
|
nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
||||||
|
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
|
||||||
|
fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
|
||||||
|
culpa qui officia deserunt mollit anim id est laborum.
|
||||||
@ -4,9 +4,11 @@ import Test.Hspec
|
|||||||
import YesodStaticTest (specs)
|
import YesodStaticTest (specs)
|
||||||
import EmbedProductionTest (embedProductionSpecs)
|
import EmbedProductionTest (embedProductionSpecs)
|
||||||
import EmbedDevelTest (embedDevSpecs)
|
import EmbedDevelTest (embedDevSpecs)
|
||||||
|
import FileGeneratorTests (fileGenSpecs)
|
||||||
|
|
||||||
main :: IO ()
|
main :: IO ()
|
||||||
main = hspec $ do
|
main = hspec $ do
|
||||||
specs
|
specs
|
||||||
embedProductionSpecs
|
embedProductionSpecs
|
||||||
embedDevSpecs
|
embedDevSpecs
|
||||||
|
fileGenSpecs
|
||||||
|
|||||||
@ -17,6 +17,9 @@ extra-source-files:
|
|||||||
test/fs/tmp/ignored
|
test/fs/tmp/ignored
|
||||||
test/fs/.ignored
|
test/fs/.ignored
|
||||||
test/fs/foo
|
test/fs/foo
|
||||||
|
test/embed-dir/foo
|
||||||
|
test/embed-dir/lorem.txt
|
||||||
|
test/embed-dir/abc/def.txt
|
||||||
|
|
||||||
library
|
library
|
||||||
build-depends: base >= 4 && < 5
|
build-depends: base >= 4 && < 5
|
||||||
@ -43,6 +46,10 @@ library
|
|||||||
, data-default
|
, data-default
|
||||||
, shakespeare-css >= 1.0.3
|
, shakespeare-css >= 1.0.3
|
||||||
, mime-types >= 0.1
|
, mime-types >= 0.1
|
||||||
|
, hjsmin
|
||||||
|
, process-conduit >= 1.0 && < 1.1
|
||||||
|
, filepath >= 1.3
|
||||||
|
, resourcet >= 0.4
|
||||||
|
|
||||||
exposed-modules: Yesod.Static
|
exposed-modules: Yesod.Static
|
||||||
Yesod.EmbeddedStatic
|
Yesod.EmbeddedStatic
|
||||||
@ -88,6 +95,10 @@ test-suite tests
|
|||||||
, data-default
|
, data-default
|
||||||
, shakespeare-css
|
, shakespeare-css
|
||||||
, mime-types
|
, mime-types
|
||||||
|
, hjsmin
|
||||||
|
, process-conduit
|
||||||
|
, filepath
|
||||||
|
, resourcet
|
||||||
|
|
||||||
ghc-options: -Wall
|
ghc-options: -Wall
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user