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

158 lines
5.0 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
2015-09-11 05:16:24 +02:00
module Main where
2015-09-11 05:08:34 +02:00
2015-09-11 06:19:03 +02:00
import Control.Monad (when)
import Data.Maybe (fromMaybe)
2015-09-21 05:19:16 +02:00
import Data.Char (isSpace)
import Data.List (isPrefixOf, find, (\\))
2015-09-11 06:19:03 +02:00
import System.Environment (lookupEnv, getProgName, getArgs)
import System.Process (callProcess)
import System.Posix.Escape.Unicode (escapeMany)
2015-09-11 16:54:29 +02:00
-- | Enviroment variables
2015-09-11 06:19:03 +02:00
type Env = [String]
2015-09-11 16:54:29 +02:00
-- | Program arguments
2015-09-11 06:19:03 +02:00
type Args = [String]
2015-09-11 16:54:29 +02:00
-- | interpreter name and arguments
2015-09-11 06:19:03 +02:00
type Inter = (String, Args)
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
2015-09-11 06:19:03 +02:00
, run :: FilePath -> Inter
2015-09-11 05:08:34 +02:00
-- ^ Command to run the given file as script
2015-09-11 06:19:03 +02:00
, repl :: FilePath -> Inter
2015-09-11 05:08:34 +02:00
-- ^ 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]
2015-09-11 16:49:40 +02:00
baseEnv = ["LOCALE_ARCHIVE", "SSL_CERT_FILE" ,"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]
2019-09-19 17:31:17 +02:00
languages = [haskell, python 2, python 3, javascript, perl, shell]
2015-09-11 05:08:34 +02:00
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])
2019-09-19 17:31:17 +02:00
python v = Language ("python" ++ show v) d r i where
d pkgs = pure ("python" ++ (show v) ++
".withPackages (py: with py; [" ++
unwords pkgs ++ "])")
r script = ("python" ++ show v, [script])
i script = ("python" ++ show v, ["-i", script])
2015-09-11 05:08:34 +02:00
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
2019-09-19 17:31:57 +02:00
d pkgs = pure ("perl.withPackages (pl: with pl; [" ++
unwords pkgs ++ "])")
2015-09-11 05:08:34 +02:00
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)
2015-09-11 06:19:03 +02:00
2015-09-21 05:19:16 +02:00
-- | Extract environment declaration from the header
filterEnv :: [String] -> (Env, [String])
filterEnv header = (vars env, header \\ env)
where
vars = concatMap (drop 2 . words)
env = filter (isPrefixOf "env" . dropWhile isSpace) header
2015-09-11 05:08:34 +02:00
-- | 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
2015-09-11 06:19:03 +02:00
makeInter :: String -> Bool -> String -> Inter
makeInter lang interactive =
2015-09-11 05:08:34 +02:00
(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
2015-09-11 06:19:03 +02:00
makeCmd :: Inter -> Args -> Env -> String
makeCmd (program, args) args' defs =
env defs ++ interpreter ++ escapeMany args'
2015-09-11 05:08:34 +02:00
where
2015-09-11 06:19:03 +02:00
interpreter = program ++ " " ++ unwords args ++ " "
2015-09-11 05:08:34 +02:00
env defs = "env " ++ unwords defs ++ " "
2015-09-11 06:19:03 +02:00
-- | Create environment variable to run the script with
2015-09-21 05:19:16 +02:00
makeEnv :: Env -> IO Env
makeEnv extra = mapM format (baseEnv ++ extra) where
2015-09-11 06:19:03 +02:00
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>")
2015-09-11 06:19:03 +02:00
let shebang = takeWhile (isPrefixOf "#!") . lines
header = drop 1 . map (drop 2) . shebang
(file:args) = progArgs
2015-09-11 05:08:34 +02:00
script <- readFile file
case header script of
2015-09-11 06:19:03 +02:00
(('>':identifier) : lines) -> do
2015-09-21 05:19:16 +02:00
let (env, deps) = filterEnv lines
pkgs = concatMap parseHeader deps
language = dropWhile isSpace identifier
2015-09-11 06:19:03 +02:00
interactive = last progName == 'i'
2019-09-19 17:32:40 +02:00
shell = last progName == 's'
2015-09-11 06:19:03 +02:00
interpreter = makeInter language interactive file
2015-09-21 05:19:16 +02:00
cmd <- makeCmd interpreter args <$> makeEnv env
2019-09-19 17:32:40 +02:00
if shell
then callProcess "nix-shell" ("-p" : pkgs)
else callProcess "nix-shell" ("--pure" : "--run" : cmd : "-p" : pkgs)
2015-09-11 05:08:34 +02:00
2019-09-19 17:32:40 +02:00
_ -> fail "missing or invalid header"