packages feed

om-elm (empty) → 1.0.0.1

raw patch · 5 files changed

+288/−0 lines, 5 filesdep +Cabaldep +basedep +bytestringsetup-changed

Dependencies added: Cabal, base, bytestring, containers, directory, http-types, safe, safe-exceptions, template-haskell, text, unix, wai

Files

+ LICENSE view
@@ -0,0 +1,20 @@+Copyright (c) 2018 Owens Murray, LLC.++Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions:++The above copyright notice and this permission notice shall be included+in all copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ README.md view
@@ -0,0 +1,10 @@+# om-elm++This package provides utilities for serving Elm programs directly+from your Haskell binary. It uses TemplateHaskell to compile your Elm+program at build time, and construct a WAI Middleware which intercepts+requests appropriate to the Elm program, and passing other requests to+a downstream WAI Application. It is useful for bundling the browser side+of a web application with its backing web services implementation.++
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ om-elm.cabal view
@@ -0,0 +1,46 @@+-- Initial om-elm.cabal generated by cabal init.  For further +-- documentation, see http://haskell.org/cabal/users-guide/++name:                om-elm+version:             1.0.0.1+synopsis:            Haskell utilities for building embedded Elm programs.+description:         This package provides utilities for serving Elm programs+                     directly from your Haskell binary. It uses TemplateHaskell+                     to compile your Elm program at build time, and construct a+                     WAI Middleware which intercepts requests appropriate to+                     the Elm program, and passing other requests to a+                     downstream WAI Application. It is useful for bundling the+                     browser side of a web application with its backing web+                     services implementation.+homepage:            https://github.com/owensmurray/om-elm+license:             MIT+license-file:        LICENSE+author:              Rick Owens+maintainer:          rick@owensmurray.com+copyright:           2018 Owens Murray, LLC.+category:            Web+build-type:          Simple+extra-source-files:  README.md+cabal-version:       >=1.10++library+  exposed-modules:     +    System.Elm.Middleware+  -- other-modules:       +  -- other-extensions:    +  build-depends:+    Cabal            >= 2.0.1.1  && < 2.1,+    base             >= 4.9      && < 4.11,+    bytestring       >= 0.10.8.1 && < 0.11,+    containers       >= 0.5.7.1  && < 0.6,+    directory        >= 1.3.0.0  && < 1.4,+    http-types       >= 0.9.1    && < 0.10,+    safe             >= 0.3.15   && < 0.4,+    safe-exceptions  >= 0.1.6.0  && < 0.2,+    template-haskell >= 2.11.1.0 && < 2.13,+    text             >= 1.2.2.2  && < 1.3,+    unix             >= 2.7.2.1  && < 2.8,+    wai              >= 3.2.1.1  && < 3.3+  hs-source-dirs: src+  default-language: Haskell2010+
+ src/System/Elm/Middleware.hs view
@@ -0,0 +1,210 @@+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE ViewPatterns #-}++{- |+  This module contains utilities for compiling and bundling Elm programs+  directly into your Haskell executable binary. It is useful for the+  case where you want to bundle a front-end Elm web app along with the+  backing services that support it, and especially for when the two+  components are part of the same codebase. It produces WAI Middleware,+  and is thus compatible with a wide range server-side frameworks.++  Usage is designed to be as simple as possible. There are 3 steps:++  1) Change your .cabal file to use a \"Custom\" build type, and add+  the appropriate custom build dependencies.++  > build-type: Custom+  > ...+  > custom-setup+  >   setup-depends:+  >     Cabal,+  >     base,+  >     om-elm++  2) Modify your @Setup.hs@ file, using 'requireElm'.++  3) Include a 'Middleware' template-haskell splice, using 'elmSite',+  in the appropriate place in your code.++  See the function documnetation for more details.++-}+module System.Elm.Middleware (+  requireElm,+  elmSite,+  elmSiteDebug,+  PathInfo,+) where+++import Control.Exception.Safe (tryAny)+import Control.Monad (void)+import Data.Bool (bool)+import Data.Map (Map)+import Data.Monoid ((<>))+import Data.String (IsString, fromString)+import Data.Text (Text)+import Data.Text.Encoding (decodeUtf8)+import Distribution.Simple (hookedPrograms, simpleUserHooks, preConf,+   UserHooks)+import Distribution.Simple.Program (simpleProgram, Program,+   configureAllKnownPrograms, requireProgram, defaultProgramDb)+import Distribution.Simple.Setup (fromFlagOrDefault, configVerbosity)+import Distribution.Verbosity (normal)+import Language.Haskell.TH (Q, TExp, runIO)+import Language.Haskell.TH.Syntax (addDependentFile)+import Network.HTTP.Types (ok200, methodNotAllowed405)+import Network.Wai (Middleware, Application, pathInfo, requestMethod,+   responseLBS)+import Safe (lastMay)+import System.Directory (removeDirectoryRecursive, createDirectory)+import System.Exit (ExitCode(ExitSuccess))+import System.Posix (ProcessStatus(Exited), forkProcess, executeFile,+   getProcessStatus)+import qualified Data.ByteString as BS+import qualified Data.Map as Map+import qualified Data.Text as T+++{- |+  Add the elm-make program requirements to a set of build hooks. This is+  expected to be used in your Setup.hs file thusly:++  > import Distribution.Simple (defaultMainWithHooks, simpleUserHooks)+  > import System.Elm.Middleware (requireElm)+  > +  > main = defaultMainWithHooks (requireElm simpleUserHooks)++-}+requireElm :: UserHooks -> UserHooks+requireElm hooks =+    hooks {+      hookedPrograms = hookedPrograms hooks ++ [elmProg],++      preConf = \args flags -> do+        let verbosity = fromFlagOrDefault normal (configVerbosity flags)+        db <- configureAllKnownPrograms verbosity defaultProgramDb+        _ <- requireProgram verbosity elmProg db+        preConf simpleUserHooks args flags+    }+  where+    {- | A description of the elm-make program.  -}+    elmProg :: Program+    elmProg = simpleProgram "elm-make"+++{- |+  Template Haskell method to create a 'Middleware' that serves a set of+  elm programs. The elm programs are compiled into HTML at compile time,+  and that HTML is included directly in your executable.++  The parameter is a map of 'pathInfo's to elm program module file. The+  elm program located at the file is served whenever the pathInfo matches+  that of the request. Any non-matching request is forwarded to the+  downstream 'Application'.++  The typed template-haskell splice:++  > $$(elmSite $ Map.fromList [+  >     (["app.js"], "elm/Your/Elm/Module/App.elm")+  >   ])++  will construct a WAI 'Middleware' which serves the compiled elm program on+  @/app.js@.++-}+elmSite :: Map PathInfo FilePath -> Q (TExp Middleware)+elmSite = elmSite2 False+++{- | Like 'elmSite', but serve the debug elm runtime. -}+elmSiteDebug :: Map PathInfo FilePath -> Q (TExp Middleware)+elmSiteDebug = elmSite2 True+++elmSite2 :: Bool -> Map PathInfo FilePath -> Q (TExp Middleware)+elmSite2 debug spec =+    buildMiddleware =<< (+      mapM (\(u, c) -> (u,) <$> c) [+        (uriPath, compileElm uriPath elmFile)+        | (fmap T.unpack -> uriPath, elmFile) <- Map.toList spec+      ]+    )+  where+    {- | Construct the middleware from a set of compiled elm resources. -}+    buildMiddleware :: [([String], (String, String))] -> Q (TExp Middleware)+    buildMiddleware resources = [||+        let+          apps = Map.fromList[+              (uriPath, buildApp contentType content)+              | (fmap T.pack -> uriPath, (contentType, content)) <- resources+            ]+          {- | Build the application that serves a single elm resource. -}+          buildApp :: String -> String -> Application+          buildApp contentType content req respond = respond $+            case requestMethod req of+              "GET" ->+                responseLBS+                  ok200+                  [("Content-Type", fromString contentType)]+                  (fromString content)+              _ -> responseLBS methodNotAllowed405 [("Allow", "GET")] ""+        in+          \downstream req respond ->+            case Map.lookup (pathInfo req) apps of+              Nothing -> downstream req respond+              Just app -> app req respond+      ||]++    compileElm :: [String] -> FilePath -> Q (String, String)+    compileElm uriPath elmFile = do+        addDependentFile elmFile+        runIO $ do+          void . tryAny $ removeDirectoryRecursive buildDir+          createDirectory buildDir+          putStrLn $ "Compiling elm file: " ++ elmFile+          forkProcess (executeFile "elm-make" True ([+              elmFile,+              "--yes",+              "--output=" <> buildFile+            ] ++ bool [] ["--debug"] debug) Nothing) >>= getProcessStatus True True >>= \case+              Nothing -> fail "elm-make should have ended."+              Just (Exited ExitSuccess) ->+                (contentType,)+                . T.unpack+                . decodeUtf8+                <$> BS.readFile buildFile+              e -> fail $ "elm-make failed with: " ++ show e+      where+        {- |+          The name of the build directory. We have to have a build+          directory because elm-make won't output compile results to+          stdout. It will only output them to files.+        -}+        buildDir :: (IsString a) => a+        buildDir = ".om-elm-build-dir"++        {- | Figure out if we are compiling to javascript or html. -}+        contentType :: String+        contentType = case lastMay uriPath of+          Just (endsWith ".js" -> True) -> "text/javascript"+          _ -> "text/html"++        buildFile :: FilePath+        buildFile = buildDir <> case lastMay uriPath of+          Just (endsWith ".js" -> True) -> "/elm.js"+          _ -> "/elm.html"++        endsWith :: String -> String -> Bool+        endsWith ending str =+          take (length ending) (reverse str) == reverse ending+++{- | A WAI uri path, as per the meaning of 'pathInfo'. -}+type PathInfo = [Text]++