snaplet-purescript-0.3.0.0: src/Snap/Snaplet/PureScript.hs
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE OverloadedStrings #-}
module Snap.Snaplet.PureScript
( initPurs
, pursServe
, module Internals
) where
import Prelude hiding (FilePath)
import Control.Monad.IO.Class
import Snap.Core
import Snap.Snaplet
import Control.Monad
import Control.Monad.State.Strict
import Paths_snaplet_purescript
import Shelly hiding (get)
import Text.Printf
import Data.Monoid
import Data.Configurator
import qualified Data.Text.Encoding as TE
import qualified Data.Text as T
import Snap.Snaplet.PureScript.Internal as Internals (PureScript)
import Snap.Snaplet.PureScript.Internal
import Text.RawString.QQ
--------------------------------------------------------------------------------
-- | Snaplet initialization
initPurs :: SnapletInit b PureScript
initPurs = makeSnaplet "purs" description (Just dataDir) $ do
config <- getSnapletUserConfig
env <- getEnvironment
buildDir <- liftIO (lookupDefault "js" config "buildDir")
verbosity <- liftIO (lookupDefault Verbose config "verbosity")
cm <- getCompilationFlavour
destDir <- getDestDir
srcDir <- fromText <$> getSrcDir
jsDir <- fromText <$> getJsDir
gruntfile <- fromText <$> getGruntfile
let envCfg = fromText $ destDir <> T.pack ("/" <> env <> ".cfg")
-- If they do not exist, create the required directories
shelly $ silently $ do
srcDirExisted <- test_d srcDir
mapM_ mkdir_p [jsDir, srcDir]
gruntFileExists <- test_f gruntfile
envCfgExists <- test_f envCfg
unlessM (localGruntInstalled (fromText destDir)) $ do
liftIO $ do
putStrLn "Local grunt not found, installing it for you..."
installLocalGrunt (fromText destDir)
unless gruntFileExists $ writefile gruntfile (gruntTemplate buildDir)
unless envCfgExists $ do
touchfile envCfg
writefile envCfg (envCfgTemplate verbosity cm)
unless srcDirExisted $ do
let mainFile = srcDir </> (fromText "Main.purs")
touchfile mainFile
writefile mainFile mainTemplate
-- compile at least once, regardless of the CompilationMode.
-- NOTE: We might want to ignore the ouput of this first compilation
-- if we are running in a 'permissive' mode, to avoid having the entire
-- web service to grind to an halt in case our Purs does not compile.
res <- compileAll (fromText destDir)
case res of
CompilationFailed reason -> fail (T.unpack reason)
CompilationSucceeded -> return ()
return $ PureScript cm verbosity
where
description = "Automatic (re)compilation of PureScript projects"
dataDir = liftM (++ "/resources") getDataDir
--------------------------------------------------------------------------------
pursLog :: String -> Handler b PureScript ()
pursLog l = do
verb <- get >>= return . pursVerbosity
unless (verb == Quiet) (liftIO $ putStrLn $ "snaplet-purescript: " <> l)
--------------------------------------------------------------------------------
localGruntInstalled :: FilePath -> Sh Bool
localGruntInstalled dir = errExit False $ chdir dir $ do
run_ "grunt" []
eC <- lastExitCode
return $ case eC of
99 -> False
_ -> True
--------------------------------------------------------------------------------
installLocalGrunt :: MonadIO m => FilePath -> m ()
installLocalGrunt dir = liftIO $ shelly $ silently $ chdir dir $ do
run_ "npm" ["install", "grunt"]
run_ "npm" ["install", "grunt-purescript"]
--------------------------------------------------------------------------------
pursServe :: Handler b PureScript ()
pursServe = do
modifyResponse . setContentType $ "text/javascript;charset=utf-8"
res <- get >>= compileWithMode . pursCompilationMode
case res of
CompilationFailed reason -> writeText reason
CompilationSucceeded -> do
-- Now get the requested file and try to serve it
-- This returns something like /purs/Hello/index.js
(_, requestedJs) <- T.breakOn "/" . T.drop 1 . TE.decodeUtf8 . rqURI <$> getRequest
case requestedJs of
"" -> fail (jsNotFound requestedJs)
_ -> do
pursLog $ "Serving " <> T.unpack requestedJs
jsDir <- getJsDir
let fulljsPath = jsDir <> requestedJs
(shelly $ silently $ readfile (fromText fulljsPath)) >>= writeText
--------------------------------------------------------------------------------
compileAll :: MonadIO m => FilePath -> m CompilationOutput
compileAll fp = liftIO $ shelly $ silently $ errExit False $ chdir fp $ do
res <- run "grunt" []
eC <- lastExitCode
case (eC == 0) of
True -> return CompilationSucceeded
False -> return $ CompilationFailed res
--------------------------------------------------------------------------------
compileWithMode :: CompilationMode -> Handler b PureScript CompilationOutput
compileWithMode CompileOnce = return CompilationSucceeded
compileWithMode CompileAlways = do
projDir <- getDestDir
pursLog $ "Compiling Purescript project at " <> T.unpack projDir
compileAll (fromText projDir)
--------------------------------------------------------------------------------
jsNotFound :: T.Text -> String
jsNotFound js = printf [r|
You asked me to serve:
%s
But I wasn't able to find a suitable PureScript module to build.
If this is the first time you are running snaplet-purescript, have
a look inside snaplets/purs/Gruntfile.js.
You probably need to uncomment the 'main:' section to make it
point to your Main.purs, as well as adding any relevant module to
your 'modules:' section.
|] (T.unpack js)
--------------------------------------------------------------------------------
gruntTemplate :: String -> T.Text
gruntTemplate = T.pack . printf [r|
module.exports = function(grunt) { "use strict"; grunt.initConfig({
srcFiles: ["src/**/*.purs", "bower_components/**/src/**/*.purs"],
psc: {
options: {
//main: "YourMainGoesHere",
modules: [] //Add your modules here
},
all: {
src: ["<%%=srcFiles%%>"],
dest: "%s/app.js"
}
}
});
grunt.loadNpmTasks("grunt-purescript");
grunt.registerTask("default", ["psc:all"]);
};
|]
--------------------------------------------------------------------------------
envCfgTemplate :: Verbosity -> CompilationMode -> T.Text
envCfgTemplate ver cm = T.pack $ printf [r|
# Choose one between 'Verbose' and 'Quiet'
verbosity = "%s"
# Choose one between 'CompileOnce' and 'CompileAlways'
compilationMode = "%s"
|] (show ver) (show cm)
--------------------------------------------------------------------------------
mainTemplate :: T.Text
mainTemplate = T.pack [r|
module Main where
import Debug.Trace
main = trace "Hello PS world!"
|]