packages feed

elm2nix (empty) → 0.1.0

raw patch · 11 files changed

+651/−0 lines, 11 filesdep +aesondep +ansi-wl-pprintdep +asyncsetup-changed

Dependencies added: aeson, ansi-wl-pprint, async, base, binary, bytestring, containers, data-default, directory, elm2nix, filepath, here, mtl, optparse-applicative, process, req, text, transformers, unordered-containers

Files

+ ChangeLog.md view
@@ -0,0 +1,5 @@+# Changelog for elm2nix++## 0.1.0 (2018-12-28)++- Initial release (@domenkozar)
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Domen Kožar (c) 2017-2019++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Domen Kožar nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,90 @@+# elm2nix++[![Build Status](https://travis-ci.org/domenkozar/elm2nix.svg?branch=master)](https://travis-ci.org/domenkozar/elm2nix)+[![Hackage](https://img.shields.io/hackage/v/elm2nix.svg)](https://hackage.haskell.org/package/elm2nix)++Convert an [Elm](http://elm-lang.org/) project into+[Nix](https://nixos.org/nix/) expressions.++It consists of multiple commands:+- `elm2nix convert`: Given `elm.json` in current directory, all dependencies are+  parsed and their sha256sum calculated+- `elm2nix snapshot`: Downloads snapshot of http://package.elm-lang.org into `versions.dat`+- `elm2nix init`: Generates `default.nix` that glues everything together++## Assumptions++Supports Elm 0.19.x++## Installation++    $ nix-shell -p stack --run "stack install --nix"++## Usage++    $ git clone https://github.com/evancz/elm-todomvc.git+    $ cd elm-todomvc+    $ ~/.local/bin/elm2nix init > default.nix+    $ ~/.local/bin/elm2nix convert > elm-srcs.nix+    $ ~/.local/bin/elm2nix snapshot > versions.dat+    $ nix-build+    $ chromium ./result/index.html++## Running tests (as per CI)++    $ ./scripts/tests.sh++## FAQ++### Why is mkDerivation inlined into `default.nix`?++As it's considered experimental, it's generated for now. Might change in the future.++### How to use with ParcelJS and Yarn?++Instead of running `elm2nix init`, use something like:++```nix+{ pkgs ? import <nixpkgs> {}+}:++let+  yarnPkg = pkgs.yarn2nix.mkYarnPackage {+    name = "myproject-node-packages";+    packageJSON = ./package.json;+    unpackPhase = ":";+    src = null;+    yarnLock = ./yarn.lock;+    publishBinsFor = ["parcel-bundler"];+  };+in pkgs.stdenv.mkDerivation {+  name = "myproject-frontend";+  src = pkgs.lib.cleanSource ./.;++  buildInputs = with pkgs.elmPackages; [+    elm+    elm-format+    yarnPkg+    pkgs.yarn+  ];++  patchPhase = ''+    rm -rf elm-stuff+    ln -sf ${yarnPkg}/node_modules .+  '';++  shellHook = ''+    ln -fs ${yarnPkg}/node_modules .+  '';++  configurePhase = pkgs.elmPackages.fetchElmDeps {+    elmPackages = import ./elm-srcs.nix;+    versionsDat = ./versions.dat;+  };++  installPhase = ''+    mkdir -p $out+    parcel build -d $out index.html+  '';+}+```
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ data/default.nix view
@@ -0,0 +1,42 @@+{ nixpkgs ? <nixpkgs>+, config ? {}+}:++with (import nixpkgs config);++let+  mkDerivation =+    { srcs ? ./elm-srcs.nix+    , src+    , name+    , srcdir ? "./src"+    , targets ? []+    , versionsDat ? ./versions.dat+    }:+    stdenv.mkDerivation {+      inherit name src;++      buildInputs = [ elmPackages.elm ];++      buildPhase = pkgs.elmPackages.fetchElmDeps {+        elmPackages = import srcs;+        inherit versionsDat;+      };++      installPhase = let+        elmfile = module: "\${srcdir}/\${builtins.replaceStrings ["."] ["/"] module}.elm";+      in ''+        mkdir -p \$out/share/doc+        \${lib.concatStrings (map (module: ''+          echo "compiling \${elmfile module}"+          elm make \${elmfile module} --output \$out/\${module}.html --docs \$out/share/doc/\${module}.json+        '') targets)}+      '';+    };+in mkDerivation {+  name = "${name}";+  srcs = ./elm-srcs.nix;+  src = ./.;+  targets = ["Main"];+  srcdir = "${srcdir}";+}
+ elm2nix.cabal view
@@ -0,0 +1,85 @@+-- This file has been generated from package.yaml by hpack version 0.18.1.+--+-- see: https://github.com/sol/hpack++name:           elm2nix+version:        0.1.0+synopsis:       Turn your Elm project into buildable Nix project+description:    Please see the README on Github at <https://github.com/domenkozar/elm2nix#readme>+homepage:       https://github.com/domenkozar/elm2nix#readme+bug-reports:    https://github.com/domenkozar/elm2nix/issues+author:         Domen Kožar+maintainer:     domen@dev.si+copyright:      2017-2019 Domen Kožar+license:        BSD3+license-file:   LICENSE+build-type:     Simple+cabal-version:  2.0++extra-source-files:+    ChangeLog.md+    README.md+    data/default.nix++source-repository head+  type: git+  location: https://github.com/domenkozar/elm2nix++library+  hs-source-dirs:+      src+  build-depends:+      base >= 4.7 && < 5+    , aeson+    , async+    , bytestring+    , binary+    , containers+    , data-default+    , directory+    , filepath+    , here+    , mtl+    , process+    , req+    , text+    , transformers+    , unordered-containers+  exposed-modules:+      Elm2Nix+      Elm2Nix.FixedOutput+      Elm2Nix.PackagesSnapshot+  other-modules:+      Paths_elm2nix+  autogen-modules:+      Paths_elm2nix+  default-language: Haskell2010++executable elm2nix+  main-is: Main.hs+  hs-source-dirs:+      elm2nix+  ghc-options: -threaded -rtsopts -with-rtsopts=-N+  autogen-modules:+      Paths_elm2nix+  other-modules:+      Paths_elm2nix+  build-depends:+      base >= 4.7 && < 5+    , elm2nix+    , directory+    , optparse-applicative+    , here+    , ansi-wl-pprint+  default-language: Haskell2010++test-suite elm2nix-test+  type: exitcode-stdio-1.0+  main-is: Spec.hs+  hs-source-dirs:+      test+  ghc-options: -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      base >= 4.7 && < 5+    , elm2nix+  default-language: Haskell2010
+ elm2nix/Main.hs view
@@ -0,0 +1,60 @@+{-# LANGUAGE QuasiQuotes #-}+module Main+  ( main+  ) where++import Data.Semigroup ((<>))+import Data.Version (showVersion)+import Data.String.Here (hereLit)+import Options.Applicative+import System.IO+import System.Directory (getCurrentDirectory)+import qualified Elm2Nix+import qualified Paths_elm2nix as This+import qualified Text.PrettyPrint.ANSI.Leijen as PP+++data Command+  = Init+  | Convert+  | Snapshot++main :: IO ()+main = do+  hSetBuffering stdout LineBuffering+  hSetBuffering stderr LineBuffering+  cmd <- getOpts+  cwd <- getCurrentDirectory+  case cmd of+    Convert -> Elm2Nix.convert+    Init -> Elm2Nix.initialize+    Snapshot -> Elm2Nix.snapshot cwd++getOpts :: IO Command+getOpts = customExecParser p (infoH opts rest)+  where+    infoH a = info (helper <*> a)+    rest = fullDesc+      <> progDesc "Convert Elm project to Nix expressions"+      <> header ("elm2nix " ++ showVersion This.version)+      <> footerDoc (Just $ PP.string exampleText)+    p = prefs showHelpOnEmpty++    exampleText :: String+    exampleText = [hereLit|++    Usage:++      $ elm2nix init > default.nix+      $ elm2nix convert > elm-srcs.nix+      $ nix-build++      Note: You have to run elm2nix from top-level directory of an Elm project.+    |]++    opts :: Parser Command+    opts = subparser+      ( command "init" (infoH (pure Init) (progDesc "Generate default.nix (printed to stdout)"))+     <> command "convert" (infoH (pure Convert) (progDesc "Generate Nix expressions for elm.json using nix-prefetch-url"))+     <> command "snapshot" (infoH (pure Snapshot) (progDesc "Generate versions.dat"))+      )
+ src/Elm2Nix.hs view
@@ -0,0 +1,142 @@+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}++module Elm2Nix+    ( convert+    , initialize+    , snapshot+    ) where++import Control.Concurrent.Async (mapConcurrently)+import Control.Monad (liftM2)+import Control.Monad.Except (liftIO, MonadIO)+import Control.Monad.Trans.Except (ExceptT, runExceptT, throwE)+import Data.Aeson (Value(..))+import Data.List (intercalate)+import Data.HashMap.Strict (HashMap)+import Data.String.Here+import Data.Text (Text)+import System.Exit (exitFailure)+import System.IO (hPutStrLn, stderr)++import qualified Data.HashMap.Strict as HM+import qualified Data.ByteString.Lazy as LBS+import qualified Data.Aeson as Json+import qualified Data.Text as Text++import Elm2Nix.FixedOutput (FixedDerivation(..), prefetch)+import Elm2Nix.PackagesSnapshot (snapshot)+++newtype Elm2Nix a = Elm2Nix { runElm2Nix_ :: ExceptT Elm2NixError IO a }+  deriving (Functor, Applicative, Monad, MonadIO)++type Dep = (String, String)++data Elm2NixError =+    ElmJsonReadError String+  | UnexpectedValue Value+  | KeyNotFound Text+  deriving Show++runElm2Nix :: Elm2Nix a -> IO (Either Elm2NixError a)+runElm2Nix = runExceptT . runElm2Nix_++throwErr :: Elm2NixError -> Elm2Nix a+throwErr e = Elm2Nix (throwE e)++parseElmJsonDeps :: Value -> Either Elm2NixError [Dep]+parseElmJsonDeps obj =+  case obj of+    Object hm -> do+      deps <- tryLookup hm "dependencies"+      case deps of+        Object dhm -> do+          direct   <- tryLookup dhm "direct"+          indirect <- tryLookup dhm "indirect"+          liftM2 (++) (parseDeps direct) (parseDeps indirect)+        v -> Left (UnexpectedValue v)+    v -> Left (UnexpectedValue v)+  where+    parseDep :: Text -> Value -> Either Elm2NixError Dep+    parseDep name (String ver) = Right (Text.unpack name, Text.unpack ver)+    parseDep _ v               = Left (UnexpectedValue v)++    parseDeps :: Value -> Either Elm2NixError [Dep]+    parseDeps (Object hm) = mapM (uncurry parseDep) (HM.toList hm)+    parseDeps v           = Left (UnexpectedValue v)++    maybeToRight :: b -> Maybe a -> Either b a+    maybeToRight _ (Just x) = Right x+    maybeToRight y Nothing  = Left y++    tryLookup :: HashMap Text Value -> Text -> Either Elm2NixError Value+    tryLookup hm key =+      maybeToRight (KeyNotFound key) (HM.lookup key hm)++-- CMDs++convert :: IO ()+convert = runCLI $ do+  liftIO (hPutStrLn stderr "Resolving elm.json dependencies into Nix ...")+  res <- liftIO (fmap Json.eitherDecode (LBS.readFile "elm.json"))+  elmJson <- either (throwErr . ElmJsonReadError) return res++  deps <- either throwErr return (parseElmJsonDeps elmJson)+  liftIO (hPutStrLn stderr "Prefetching tarballs and computing sha256 hashes ...")++  sources <- liftIO (mapConcurrently (uncurry prefetch) deps)+  liftIO (putStrLn (generateNixSources sources))++initialize :: IO ()+initialize = runCLI $+  liftIO (putStrLn [template|data/default.nix|])+  where+    -- | Converts Package.Name to Nix friendly name+    baseName :: Text+    baseName = "elm-app"+    version :: Text+    version = "0.1.0"+    toNixName :: Text -> Text+    toNixName = Text.replace "/" "-"+    name :: String+    name = Text.unpack (toNixName baseName <> "-" <> version)+    srcdir :: String+    srcdir = "./src" -- TODO: get from elm.json++-- Utils++runCLI :: Elm2Nix a -> IO a+runCLI m = do+  result <- runElm2Nix m+  case result of+        Right a ->+          return a+        Left err -> do+          depErrToStderr err+          exitFailure++depErrToStderr :: Elm2NixError -> IO ()+depErrToStderr err =+  hPutStrLn stderr $+      case err of+        UnexpectedValue v -> "Unexpected Value: \n" ++ show v+        ElmJsonReadError s -> "Error reading json: " ++ s+        KeyNotFound key -> "Key not found in json: " ++ Text.unpack key++generateNixSources :: [FixedDerivation] -> String+generateNixSources dss =+  [iTrim|+{+${intercalate "\n" (map f dss)}+}+  |]+  where+    f :: FixedDerivation -> String+    f ds =+      [i|+      "${drvName ds}" = {+        sha256 = "${drvHash ds}";+        version = "${drvVersion ds}";+      };|]
+ src/Elm2Nix/FixedOutput.hs view
@@ -0,0 +1,54 @@+{-# LANGUAGE OverloadedStrings #-}+module Elm2Nix.FixedOutput+  ( FixedDerivation(..)+  , prefetch+  ) where++import System.Exit+import System.Process+import qualified Data.ByteString.Lazy.Char8 as BS+++-- fixed output derivation metadata+data FixedDerivation = FixedDerivation+  { drvHash :: String -- ^ Computed sha256 hash+  , drvPath :: String -- ^ Nix store path of the derivation+  , drvUrl :: String -- ^ URL to the tarball+  , drvName :: String+  , drvVersion :: String+  } deriving (Show, Eq)++-- | Use nix-prefetch-url to obtain resulting path and it's hash+-- | Partially taken from cabal2nix/src/Distribution/Nixpkgs/Fetch.hs+prefetch :: String -> String -> IO FixedDerivation+prefetch name version = do+  (Nothing, Just stdoutH, _, processH) <-+    createProcess (proc "nix-prefetch-url" args) { env     = Nothing+                                                 , std_in  = Inherit+                                                 , std_err = Inherit+                                                 , std_out = CreatePipe+                                                 }+  exitCode <- waitForProcess processH+  case exitCode of+    ExitFailure _ -> error "nix-prefetch-url exited with non-zero"+    ExitSuccess   -> do+      buf <- BS.hGetContents stdoutH+      let ls = BS.lines buf+      case length ls of+        0 -> error "unknown nix-prefetch-url output"+        2 -> return $ FixedDerivation (BS.unpack $ head ls)+                                       (BS.unpack $ head $ tail ls)+                                       url+                                       name+                                       version+        _ -> error "unknown nix-prefetch-url output"+  where+  url :: String+  url =+    "https://github.com/"+      ++ name+      ++ "/archive/"+      ++ version+      ++ ".tar.gz"+  args :: [String]+  args = ["--print-path", url]
+ src/Elm2Nix/PackagesSnapshot.hs view
@@ -0,0 +1,139 @@+{- Downloads binary serialized https://package.elm-lang.org/all-packages+   as Elm compiler expects it to parse.++  Takes Elm upstream code from:+  - https://github.com/elm/compiler/blob/master/builder/src/Deps/Cache.hs+  - https://github.com/elm/compiler/blob/master/builder/src/Deps/Website.hs+  - https://github.com/elm/compiler/blob/master/compiler/src/Elm/Package.hs++-}+{-# LANGUAGE OverloadedStrings #-}+module Elm2Nix.PackagesSnapshot+  ( snapshot+  ) where++import Control.Monad (liftM2, liftM3)+import qualified Data.Aeson as Aeson+import qualified Data.Binary as Binary+import Data.Binary (Binary, put, get, putWord8, getWord8)+import qualified Data.Map as Map+import Data.Default (def)+import Data.Map (Map)+import Data.Text (Text)+import qualified Data.Text as Text+import Data.Word (Word16)+import qualified Network.HTTP.Req as Req+import System.FilePath ((</>))+++data Name =+  Name+    { _author :: !Text+    , _project :: !Text+    }+    deriving (Eq, Ord)++data Package =+  Package+    { _name :: !Name+    , _version :: !Version+    }+    deriving (Eq, Ord)++data Version =+  Version+    { _major :: {-# UNPACK #-} !Word16+    , _minor :: {-# UNPACK #-} !Word16+    , _patch :: {-# UNPACK #-} !Word16+    }+    deriving (Eq, Ord)++data PackageRegistry =+  PackageRegistry Int (Map Name [Version])++instance Binary Name where+  get =+    liftM2 Name get get++  put (Name author project) =+    do  put author+        put project++instance Binary Package where+  get =+    liftM2 Package get get++  put (Package name version) =+    do  put name+        put version++instance Binary Version where+  get =+    do  word <- getWord8+        if word == 0+          then liftM3 Version get get get+          else+            do  minor <- fmap fromIntegral getWord8+                patch <- fmap fromIntegral getWord8+                return (Version (fromIntegral word) minor patch)++  put (Version major minor patch) =+    if major < 256 && minor < 256 && patch < 256 then+      do  putWord8 (fromIntegral major)+          putWord8 (fromIntegral minor)+          putWord8 (fromIntegral patch)+    else+      do  putWord8 0+          put major+          put minor+          put patch++instance Binary PackageRegistry where+  get = liftM2 PackageRegistry get get+  put (PackageRegistry a b) = put a >> put b++snapshot :: String -> IO ()+snapshot dir = do+  r <- Req.runReq def $+    Req.req+    Req.POST+    (Req.https "package.elm-lang.org" Req./: "all-packages")+    Req.NoReqBody+    Req.jsonResponse+    mempty+  let packages = unwrap $ case Aeson.fromJSON (Req.responseBody r) of+         Aeson.Error s -> error s+         Aeson.Success val -> val+      size = Map.foldr ((+) . length) 0 packages+      registry = PackageRegistry size packages+  Binary.encodeFile (dir </> "versions.dat") registry++newtype Packages = Packages { unwrap :: Map.Map Name [Version] }++instance Aeson.FromJSON Packages where+  parseJSON v = Packages <$> Aeson.parseJSON v++instance Aeson.FromJSON Version where+  parseJSON = Aeson.withText "string" $ \x ->+    case Text.splitOn "." x of+      [major, minor, patch] ->+        return $ Version+                  (read (Text.unpack major))+                  (read (Text.unpack minor))+                  (read (Text.unpack patch))+      _ ->+        fail "failure parsing version"+++instance Aeson.FromJSON Name where+  parseJSON = Aeson.withText "string" $ \x ->+    case Text.splitOn "/" x of+      [author, package] -> return $ Name author package+      lst -> fail $ "wrong package name: " <> show lst+++instance Aeson.FromJSONKey Name where+  fromJSONKey = Aeson.FromJSONKeyTextParser $ \x ->+    case Text.splitOn "/" x of+      [author, package] -> return $ Name author package+      lst -> fail $ "wrong package name: " <> show lst
+ test/Spec.hs view
@@ -0,0 +1,2 @@+main :: IO ()+main = putStrLn "Test suite not yet implemented"