packages feed

nix-tools-0.1.0.0: cabal2nix/Main.hs

{-# LANGUAGE LambdaCase, RecordWildCards #-}

module Main where

import System.Environment (getArgs)

import System.Directory
import System.FilePath
import Control.Monad

import Nix.Pretty (prettyNix)
import Nix.Expr

import Data.String (fromString)

import Cabal2Nix
import Cabal2Nix.Util

import Data.Text.Prettyprint.Doc (Doc)
import Data.Text.Prettyprint.Doc.Render.Text (hPutDoc)
import System.IO
import Distribution.Nixpkgs.Fetch
import Control.Monad.IO.Class
import Control.Monad.Trans.Maybe
import Data.ByteString (ByteString)

import Data.List (isPrefixOf, isSuffixOf)

import qualified Hpack
import qualified Hpack.Config as Hpack
import qualified Hpack.Render as Hpack
import qualified Data.Text as T
import qualified Data.Text.Encoding as T


writeDoc :: FilePath -> Doc ann -> IO ()
writeDoc file doc =
  do handle <- openFile file WriteMode
     hPutDoc handle doc
     hClose handle

main :: IO ()
main = getArgs >>= \case
  [url,hash] | "http" `isPrefixOf` url ->
          let subdir = "." in
          fetch (\dir -> cabalFromPath url hash subdir $ dir </> subdir)
            (Source url mempty UnknownHash subdir) >>= \case
            (Just (DerivationSource{..}, genBindings)) -> genBindings derivHash
            _ -> return ()
  [path,file] -> doesDirectoryExist file >>= \case
    False -> print . prettyNix =<< cabal2nix False MinimalDetails (Just (Path path)) (OnDisk file)
    True  -> print . prettyNix =<< cabalexprs file
  [file] -> doesDirectoryExist file >>= \case
    False -> print . prettyNix =<< cabal2nix False MinimalDetails (Just (Path ".")) (OnDisk file)
    True  -> print . prettyNix =<< cabalexprs file
  _ -> putStrLn "call with cabalfile (Cabal2Nix file.cabal)."

cabalFromPath
  :: String    -- URL
  -> String    -- Revision
  -> FilePath  -- Subdir
  -> FilePath  -- Local Directory
  -> MaybeT IO (String -> IO ())
cabalFromPath url rev subdir path = do
  d <- liftIO $ doesDirectoryExist path
  unless d $ fail ("not a directory: " ++ path)
  cabalFiles <- liftIO $ findCabalFiles path
  when (length cabalFiles > 1) $ fail ("multiple cabal files detected: " ++ show cabalFiles)
  return $ \sha256 ->
    void . forM cabalFiles $ \cabalFile -> do
      let pkg = cabalFilePkgName cabalFile
          subdir' = if subdir == "." then Nothing
                    else Just subdir
          src = Just $ Git url rev (Just sha256) subdir'
      print . prettyNix =<< cabal2nix False MinimalDetails src cabalFile

findCabalFiles :: FilePath -> IO [CabalFile]
findCabalFiles path = doesFileExist (path </> Hpack.packageConfig) >>= \case
  False -> fmap (OnDisk . (path </>)) . filter (isSuffixOf ".cabal") <$> listDirectory path
  True -> do
    mbPkg <- Hpack.readPackageConfig Hpack.defaultDecodeOptions {Hpack.decodeOptionsTarget = path </> Hpack.packageConfig}
    case mbPkg of
      Left e -> error e
      Right r ->
        return $ [InMemory (Just Hpack)
                           (Hpack.decodeResultCabalFile r)
                           (encodeUtf8 $ Hpack.renderPackage [] (Hpack.decodeResultPackage r))]

  where encodeUtf8 :: String -> ByteString
        encodeUtf8 = T.encodeUtf8 . T.pack


expr :: FilePath -> String -> String -> IO (Binding NExpr)
expr p pkg version = do
  let cabal = OnDisk $ p </> pkg </> version </> pkg <.> "cabal"
      -- prefix packages by the truncated sha256
      -- over their name to prevent case insensitivity
      -- issues.  We truncate just to be in line with
      -- how the /nix/store path's look.
      pkg'  =       (take 32 $ sha256 pkg) ++ "-" ++ pkg
      nix   =       pkg' </> version <.> "nix"
      version' = fromString . quoted $ version
  doesFileExist (cabalFilePath cabal) >>= \case
    True ->
      do createDirectoryIfMissing True pkg'
         writeDoc nix =<< prettyNix <$> cabal2nix False MinimalDetails Nothing cabal
         pure $ version' $= mkRelPath nix
    False -> pure $ version' $= mkNull

cabalexprs :: FilePath -> IO NExpr
cabalexprs p =
  do pkgs <- listDirectories p
     fmap mkNonRecSet . forM pkgs $ \pkg ->
       do versions <- listDirectories (p </> pkg)
          let pkg' = fromString . quoted $ pkg
          fmap (bindTo pkg' . mkNonRecSet) . forM versions $ \version ->
            expr p pkg version