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

initial commit

This commit is contained in:
Benno Fünfstück 2014-08-31 16:59:47 +02:00
commit 12274118ae
3 changed files with 154 additions and 0 deletions

36
README.md Normal file
View File

@ -0,0 +1,36 @@
nix-bang
========
The Nix-bang script allows you to define dependencies at the top of scripts which will then be used to create a nix-shell environment when run. This project is only useful if you're using the nix package manager.
Installation
------------
Clone the repository and run:
```
$ nix-env -if.
```
This will install the `nix-bang` and `nix-bangi` files into your user profile.
Usage
-----
To use `nix-bang`, you need to add a header to your file. Here is an example for a haskell file:
```haskell
#!nix-bang
#!> haskell
#! haskell | text lens optparse-applicative
#! shell | nix nix-prefetch-scripts
```
The first line just tells the shell to use `nix-bang` when executing the script. The next line is then read by `nix-bang` to determine the language used for running the script. In this case, we tell `nix-bang` that we want haskell, so it will use `runhaskell` to execute the script.
The next lines the specify dependencies of the script. The first entry on each line is the language of the following dependencies. This is required so that language-specific names can be converted to the correct nix attribute names. You should have one line per language. In our case, we say that we want to use the `text`, `lens` and `optparse-applicative` haskell packages. We also want that `nix` and `nix-prefetch-scripts` are available in $PATH (the `shell` language doesn't apply any renaming to their dependencies and just passes them through unmodified).
Contributing
------------
If you want to add support for another language, or just have a good improvment you'd like to implement, feel free to fork the repository and then submit a pull request. You can find me on irc.freenode.org as bennofs in the #nixos channel if you have questions.

17
default.nix Normal file
View File

@ -0,0 +1,17 @@
{ pkgs ? import <nixpkgs> {} }:
pkgs.stdenv.mkDerivation {
name = "nix-bang";
src = ./nix-bang.hs;
phases = [ "buildPhase" "installPhase" "fixupPhase" ];
buildPhase = ''mkdir $out; ghc -O2 $src -o $out/nix-bang -odir $TMP'';
installPhase = ''ln -s $out/nix-bang $out/nix-bangi'';
buildInputs = [ (pkgs.haskellPackages.ghcWithPackages (hs: with hs; [lens text])) ];
meta = {
homepage = https://github.com/bennofs/nix-bang;
description = "A shebang for running inside nix-shell.";
license = pkgs.lib.licenses.bsd3;
maintainers = [ pkgs.lib.maintainers.bennofs ];
platforms = pkgs.haskellPackages.ghc.meta.platforms;
};
}

101
nix-bang.hs Executable file
View File

@ -0,0 +1,101 @@
#!nix-bang
#!> haskell
#! haskell | text lens
#! shell | nix
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TupleSections #-}
import Control.Monad
import Control.Applicative
import System.Environment
import Data.List
import Data.Text (Text)
import Data.Text.Lens (_Text)
import Control.Lens
import Control.Exception.Lens
import System.IO.Error.Lens
import System.Exit
import System.Posix.Process
import System.Posix.IO
import System.IO
import Data.Char
import Data.Monoid
import qualified Data.Text as Text
-- | Information about a languages
data LangDef = LangDef
{ name :: String -- ^ Name of this language
, deps :: [Text] -> [Text] -- ^ Convert langunage-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
}
languages :: [LangDef]
languages = [haskell, shell]
haskell :: LangDef
haskell = LangDef "haskell" d r i where
d pkgs = return $
"haskellPackages.ghcWithPackages (hs: with hs; [" <> Text.unwords (map mkPkg pkgs) <> "])"
mkPkg = Text.concat . over (_tail.mapped._head) toUpper . Text.splitOn "-"
r script = ("runhaskell" , [script])
i script = ("ghci" , [script])
shell :: LangDef
shell = LangDef "shell" (extraPackages ++) r i where
r script = ("bash", [script])
i _ = ("bash", [])
extraPackages = ["bash", "coreutils", "utillinux", "gitAndTools.hub", "git"]
lookupLangDef :: String -> IO LangDef
lookupLangDef n
| Just def <- find ((n ==) . name) languages = return def
| otherwise = fail $ "Unknown language: " ++ n
makeDeps :: String -> [String] -> IO [String]
makeDeps lang ds = lookupLangDef lang <&> \def ->
map (view _Text) $ deps def (map (review _Text) ds)
parseDepLine :: [String] -> IO (String, [String])
parseDepLine (lang:"|":deps) = return (lang, deps)
parseDepLine x = fail $ "Invalid dependency specification: " ++ unwords x
makeCommand :: String -> Bool -> String -> IO (String, [String])
makeCommand lang interactive file = lookupLangDef lang <&> \def ->
(if interactive then repl else run) def file
makeEnvArg :: String -> IO String
makeEnvArg env = f $ getEnv env <&> \val -> env ++ "=" ++ val where
f = handling_ (_IOException.errorType._NoSuchThing) $ return ""
makeXargsCommand :: String -> Int -> IO String
makeXargsCommand cmd fd = do
let xargsFile = "/proc/self/fd/" ++ show fd
envStr <- unwords <$> traverse makeEnvArg
["LOCALE_ARCHIVE", "LANG", "TERMINFO", "TERM"]
return $ "env " ++ envStr ++ " xargs -a " ++ xargsFile ++ " -d '\\n' " ++ cmd ++ ""
main :: IO ()
main = do
progName <- getProgName
args <- getArgs
let interactive = "i" `isSuffixOf` progName
case args ^? _Cons of
Nothing -> fail $ "usage: " ++ progName ++ " <file>" ++ " [missing file name]"
Just (file, args') -> do
header <- drop 1 . map (drop 2) . takeWhile ("#!" `isPrefixOf`) . lines <$> readFile file
case header ^? _Cons of
Just ('>':lang, depHeader) -> do
deps <- concat <$> traverse (uncurry makeDeps <=< parseDepLine . words) depHeader
let deps' = "findutils" : deps
let depArgs = concatMap (\x -> ["-p", x]) deps'
(cmd,cmdArgs) <- makeCommand (under _Text Text.strip lang) interactive file
(readFd, writeFd) <- createPipe
writeH <- fdToHandle writeFd
hPutStrLn writeH (unlines cmdArgs) >> hFlush writeH
hClose writeH
xargsCmd <- makeXargsCommand cmd (fromIntegral readFd)
let finalArgs = "--pure" : "--command" : xargsCmd : depArgs
executeFile "nix-shell" True finalArgs Nothing
_ -> fail "missing language to run as"