haskellscript (empty) → 0.1.0.1
raw patch · 4 files changed
+150/−0 lines, 4 filesdep +basedep +cryptohashdep +directorysetup-changed
Dependencies added: base, cryptohash, directory, either, filepath, mtl, process, text
Files
- LICENSE +12/−0
- Setup.hs +2/−0
- haskellscript.cabal +31/−0
- src/HaskellScript.hs +105/−0
+ LICENSE view
@@ -0,0 +1,12 @@+Copyright (c) 2015, Sean Parsons+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.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ haskellscript.cabal view
@@ -0,0 +1,31 @@+name: haskellscript+version: 0.1.0.1+synopsis: Command line tool for running Haskell scripts with a shebang.+description: Command line tool for running Haskell scripts with a shebang.+license: BSD3+license-file: LICENSE+author: Sean Parsons+maintainer: github@futurenotfound.com+copyright: Copyright (C) 2015 Sean Parsons+category: Development+build-type: Simple+cabal-version: >=1.10+homepage: http://github.com/seanparsons/haskellscript/+bug-reports: http://github.com/seanparsons/haskellscript/issues++source-repository head+ type: git+ location: git://github.com/seanparsons/haskellscript.git++executable haskellscript+ main-is: HaskellScript.hs+ build-depends: base >=4.6 && <4.9,+ either,+ directory,+ filepath,+ text,+ mtl,+ process,+ cryptohash+ hs-source-dirs: src+ default-language: Haskell2010
+ src/HaskellScript.hs view
@@ -0,0 +1,105 @@+{-# LANGUAGE OverloadedStrings #-}++module Main where++import Control.Monad+import Control.Monad.Trans+import Control.Monad.Trans.Either+import Crypto.Hash+import Data.Either.Combinators+import Data.Traversable+import qualified Data.List as L+import System.Directory+import System.Environment (getArgs)+import System.FilePath.Posix+import System.Process+import System.IO hiding (readFile, writeFile)+import Prelude hiding (readFile, writeFile, length, take, drop, FilePath, lines, unlines)+import qualified Prelude as P+import Data.Text hiding (filter)+import qualified Data.Text.IO as TIO+import qualified Data.Text.Encoding as TE++data ScriptingError = ScriptReadError+ | ScriptParseError String+ deriving (Eq, Show)++data ScriptDetails = ScriptDetails + { scriptDependencies :: [Text]+ , scriptText :: Text+ }++main :: IO ()+main = do+ args <- getArgs+ case args of+ [script] -> runScript script+ _ -> putStrLn "Command line arguments are incorrect, please pass a script file."+++runScript :: P.FilePath -> IO ()+runScript scriptPath = do+ result <- runEitherT $ runScriptWithValidation scriptPath+ case result of+ (Right _) -> return ()+ (Left error) -> putStrLn "Error handling script: " >> print error++isDependencyLine :: Text -> Bool+isDependencyLine line = isPrefixOf "--#" line+++parseScript :: Text -> Either ScriptingError ScriptDetails+parseScript script = do+ let scriptLines = lines script+ afterShebang <- case scriptLines of+ first : rest -> if isPrefixOf "#!" first then Right rest else Left $ ScriptParseError "No shebang at start of script."+ _ -> Left $ ScriptParseError "Script is empty."+ let (dependenciesLines, remainder) = L.span (\line -> isDependencyLine line || (length $ strip line) == 0) afterShebang+ let dependencies = L.sort $ fmap (strip . drop 3) $ filter isDependencyLine dependenciesLines+ return $ ScriptDetails dependencies (unlines remainder)+++getDependenciesHash :: ScriptDetails -> String+getDependenciesHash details = + let tohashBytes = TE.encodeUtf8 $ intercalate "," $ scriptDependencies details+ in show (hash tohashBytes :: Digest SHA3_512)+++getContentHash :: ScriptDetails -> String+getContentHash details =+ let tohashBytes = TE.encodeUtf8 $ scriptText details+ in show (hash tohashBytes :: Digest SHA3_512)+++runInWorkingDir :: FilePath -> FilePath -> [String] -> EitherT ScriptingError IO ()+runInWorkingDir workingDir toRun params = lift $ do+ (_, _, _, sandboxInitHandle) <- createProcess (proc toRun params){ cwd = Just workingDir }+ waitForProcess sandboxInitHandle+ return ()+++runScriptWithValidation :: P.FilePath -> EitherT ScriptingError IO ()+runScriptWithValidation scriptPath = do+ -- Read file into memory.+ fileContents <- lift $ TIO.readFile scriptPath+ -- Parse file into dependencies and the remaining code to run.+ scriptDetails <- hoistEither $ parseScript fileContents+ -- If sandbox containing hash is not present:+ homeDirectory <- lift getHomeDirectory+ let dependenciesHash = getDependenciesHash scriptDetails+ let scriptHash = getContentHash scriptDetails+ let scriptDir = homeDirectory </> ".haskellscript" </> dependenciesHash+ scriptDirExists <- lift $ doesDirectoryExist $ scriptDir+ unless scriptDirExists $ do+ -- Create hashed path directory.+ lift $ createDirectoryIfMissing True scriptDir+ -- Init sandbox in directory.+ runInWorkingDir scriptDir "cabal" ["sandbox", "init"]+ -- For each dependency install it into the sandbox.+ traverse (\dep -> runInWorkingDir scriptDir "cabal" ["install", (unpack dep)]) (scriptDependencies scriptDetails)+ return ()+ let scriptLocation = scriptDir </> scriptHash <.> "hs"+ -- Create a file containing the code.+ lift $ TIO.writeFile scriptLocation (scriptText scriptDetails)+ -- Use cabal to runhaskell the script created.+ runInWorkingDir scriptDir "cabal" ["exec", "runghc", scriptLocation]