{-# LANGUAGE OverloadedStrings , ScopedTypeVariables #-} {-| Utilities for turning statements into full scripts. -} module Language.Bash.Script where import Data.Binary.Builder (Builder, fromByteString) import Data.ByteString.Char8 (ByteString, append) import qualified Data.ByteString.Char8 import qualified Data.ByteString.Lazy import Data.Foldable import Data.Monoid import qualified Data.Digest.Pure.SHA import Language.Bash.Syntax import Language.Bash.Lib import Language.Bash.Annotations import Language.Bash.PrettyPrinter import Language.Bash.PrettyPrinter.State {-| Produce a script beginning with @#!\/bin\/bash@ and a safe set statement. -} script :: (Annotation t) => Statement t -> Builder script statement = mconcat [ fromByteString "#!/bin/bash\n" , builder (setSafe :: Statement ()) , fromByteString "\n\n" , builder statement ] {-| Produce a script beginning with @#!\/bin\/bash@ and some (optional) documentation. Cause the script to be scanned for SHA-1 hash of the setup (first statement argument) and main (second statement argument) before running the safe set statement and running the second argument. -} script_sha1 :: forall t t'. (Annotation t, Annotation t') => ByteString -> Statement t -> Statement t' -> Builder script_sha1 docs setup main = mconcat [ fromByteString "#!/bin/bash\n\n" , remarks , fromByteString "######## Setup." , fromByteString "\n\n" , fromByteString setup' , fromByteString "\n\n" , fromByteString "######## Main." , fromByteString "\n\n" , builder tokenCheck' ] where setup' = bytes setup main' = bytes main mainSafe = Sequence (dance setSafe) (dance main) token = sha1 (append setup' main') tokenCheck' :: Statement (Statements (Statements t' ()) ()) tokenCheck' = tokenCheck token mainSafe remarks | docs == mempty = fromByteString "" | otherwise = fromByteString (docs `mappend` "\n\n") {-| Scan @$0@ for the token before running, producing a statement annotated with the initial statement. This is a bit clumsy but is used internally. -} tokenCheck :: ByteString -> Statement t -> Statement (Statements t t') tokenCheck token stmt = IfThen (Annotated (Statements noop noop) (tokenFGREPq token)) (dance stmt) {-| Scan @$0@ for the token before running, correctly producing monoidal annotations. The function argument provides an annotation for the @fgrep@ check generated to search for the token. (@const mempty@ would be appropriate in most cases.) -} mtokenCheck :: (Monoid t) => ByteString -> (Statement t -> t) -> Statement t -> Statement t mtokenCheck token f statement = IfThen (Annotated (f check) check) (Annotated (fold statement) statement) where check = tokenFGREPq token tokenFGREPq :: ByteString -> Statement t tokenFGREPq token = SimpleCommand "fgrep" ["-q", literal token, ReadVar (VarSpecial Dollar0)] {-| Scan @$0@ the SHA1 of the statement before running. -} sha1Check :: (Annotation t, Annotation t') => Statement t -> Statement (Statements t t') sha1Check stmt = tokenCheck ((sha1 . bytes) stmt) stmt sha1 :: ByteString -> ByteString sha1 = Data.ByteString.Char8.pack . Data.Digest.Pure.SHA.showDigest . Data.Digest.Pure.SHA.sha1 . Data.ByteString.Lazy.fromChunks . (:[]) {-| The noop dance -- annotate a 'NoOp' with a statement, essentially as a type coercion. -} dance :: Statement t -> Annotated (Statements t t') dance stmt = Annotated (Statements stmt noop) noop noop :: Statement any noop = NoOp ""