mirror of
https://github.com/bennofs/nix-script
synced 2025-01-10 04:44:21 +01:00
initial commit
This commit is contained in:
commit
12274118ae
36
README.md
Normal file
36
README.md
Normal 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
17
default.nix
Normal 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
101
nix-bang.hs
Executable 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"
|
Loading…
Reference in New Issue
Block a user