1
0
mirror of https://github.com/bennofs/nix-script synced 2025-01-10 12:54:20 +01:00
nix-script/nix-script.hs

133 lines
4.3 KiB
Haskell
Raw Normal View History

2015-09-11 05:08:34 +02:00
-- | A shebang for running scripts inside nix-shell with defined dependencies
module NixScript where
import Control.Monad (when)
import Data.Maybe (fromMaybe)
import Data.List (isSuffixOf, isPrefixOf, find)
import System.Environment (lookupEnv, getProgName, getArgs)
import System.Process (callProcess)
import System.Posix.IO (createPipe, fdToHandle)
import System.IO (hPutStrLn, hClose, hFlush)
2014-08-31 16:59:47 +02:00
2015-09-10 19:50:14 +02:00
-- | Information about a language
2015-09-11 05:08:34 +02:00
data Language = Language
{ name :: String
-- ^ Name of the language
, depsTrans :: [String] -> [String]
-- ^ Transform language-specific dependencies to nix packages
, run :: FilePath -> (String, [String])
-- ^ Command to run the given file as script
, repl :: FilePath -> (String, [String])
-- ^ Command to load the given file in an interpreter
2014-08-31 16:59:47 +02:00
}
2015-09-10 19:50:14 +02:00
2015-09-11 05:08:34 +02:00
-- | Basic packages always present
basePackages :: [String]
2015-09-10 19:50:14 +02:00
basePackages = ["coreutils", "utillinux"]
2015-09-11 05:08:34 +02:00
-- | Preserved environment variables
baseEnv :: [String]
baseEnv = ["LOCALE_ARCHIVE", "LANG", "TERMINFO", "TERM"]
2015-09-10 19:50:14 +02:00
2014-08-31 16:59:47 +02:00
2015-09-11 05:08:34 +02:00
-- | List of supported language definitions
languages :: [Language]
languages = [haskell, python, javascript, perl, shell]
where
haskell = Language "haskell" d r i where
d pkgs = pure ("haskellPackages.ghcWithPackages (hs: with hs; [" ++
unwords pkgs ++ "])")
r script = ("runghc" , [script])
i script = ("ghci" , [script])
python = Language "python" d r i where
d pkgs = "python" : map ("pythonPackages." ++) pkgs
r script = ("python" , [script])
i script = ("python" , ["-i", script])
javascript = Language "javascript" d r i where
d pkgs = "node" : map ("nodePackages." ++) pkgs
r script = ("node" , [script])
i script = ("node" , [])
perl = Language "perl" d r i where
d pkgs = "perl" : map ("perlPackages." ++) pkgs
r script = ("perl" , [script])
i script = ("perl" , ["-d", script])
shell = Language "shell" d r i where
d = mappend ("bash" : basePackages)
r script = ("bash", [script])
i _ = ("bash", [])
-- | Create ad-hoc definitions for unknown languages
passthrough :: String -> Language
passthrough name = Language name d r i where
2015-09-10 19:50:14 +02:00
d = mappend basePackages
r script = (name, [script])
i _ = (name, [])
2014-08-31 16:59:47 +02:00
2015-09-11 05:08:34 +02:00
-- | Find the appropriate language definition
lookupLang :: String -> Language
lookupLang n =
fromMaybe (passthrough n) (find ((n ==) . name) languages)
-- | Parse dependencies declaration line
parseHeader :: String -> [String]
parseHeader = uncurry trans . split . words
where
trans lang = depsTrans (lookupLang lang)
split (lang : "|" : deps) = (lang, deps)
split line = error ("Invalid dependency declaration: " ++ unwords line)
2014-08-31 16:59:47 +02:00
2015-09-11 05:08:34 +02:00
-- | Find command to run/load the script
interpreter :: String -> Bool -> String -> (String, [String])
interpreter lang interactive =
(if interactive then repl else run) (lookupLang lang)
2014-08-31 16:59:47 +02:00
2015-09-11 05:08:34 +02:00
-- | Create command to add the shell environment
makeCommand :: String -> [String] -> IO String
makeCommand program args = do
(readFd, writeFd) <- createPipe
writeH <- fdToHandle writeFd
hPutStrLn writeH (unlines args)
hFlush writeH >> hClose writeH
2014-08-31 16:59:47 +02:00
2015-09-11 05:08:34 +02:00
definitions <- mapM format baseEnv
return (env definitions ++ xargs readFd ++ program)
where
env defs = "env " ++ unwords defs ++ " "
xargs fd = "xargs -a /proc/self/fd/" ++ show fd ++ " -d '\\n' "
format var = maybe "" (\x -> var ++ "=" ++ x) <$> lookupEnv var
2014-08-31 16:59:47 +02:00
2015-09-11 05:08:34 +02:00
-- | run a script or load it in an interactive interpreter
2014-08-31 16:59:47 +02:00
main :: IO ()
main = do
progName <- getProgName
2015-09-11 05:08:34 +02:00
progArgs <- getArgs
when (null progArgs) (fail $ "usage: " ++ progName ++ " <file>")
let file = head progArgs
shebang = takeWhile (isPrefixOf "#!") . lines
header = drop 1 . map (drop 2) . shebang
script <- readFile file
case header script of
(('>' : identifier) : lines) -> do
let pkgs = concatMap parseHeader lines
language = dropWhile (==' ') identifier
interactive = isSuffixOf "i" progName
(program, args) = interpreter language interactive file
cmd <- makeCommand program args
callProcess "nix-shell" ("--pure" : "--command" : cmd : "-p" : pkgs)
_ -> fail "missing or invalid header"