packages feed

jenga (empty) → 0.1.0.0

raw patch · 9 files changed

+363/−0 lines, 9 filesdep +Cabaldep +aesondep +basesetup-changed

Dependencies added: Cabal, aeson, base, bytestring, containers, http-conduit, http-types, jenga, text

Files

+ ChangeLog.md view
@@ -0,0 +1,5 @@+# Revision history for jenga++## 0.1.0.0  -- YYYY-mm-dd++* First version. Released on an unsuspecting world.
+ Jenga/Cabal.hs view
@@ -0,0 +1,63 @@+module Jenga.Cabal+  ( dependencyName+  , readPackageDependencies+  ) where++-- You would thing that since the Cabal file exposes its cabal parser you would+-- think it would be a simple matter to extract the list of dependencies.+-- Unfortunately its much more work than it should be. See:+-- https://hackage.haskell.org/package/Cabal-1.24.2.0/docs/Distribution-PackageDescription.html#v:buildDepends++import qualified Data.Map.Strict as DM+import           Data.Text (Text)+import qualified Data.Text as T++import           Distribution.Package (Dependency (..), PackageIdentifier (..), PackageName (..))+import           Distribution.PackageDescription+                    ( Benchmark, CondTree (..), ConfVar, Executable, GenericPackageDescription (..)+                    , PackageDescription (..), Library, TestSuite+                    )+import           Distribution.PackageDescription.Parse (readPackageDescription)+import           Distribution.Verbosity (normal)+++readPackageDependencies :: FilePath -> IO [Dependency]+readPackageDependencies fpath = do+  genpkg <- readPackageDescription normal fpath+  pure+    $ sortNubByName+    $ filterPackageName (package $ packageDescription genpkg)+    $ extractLibraryDeps (condLibrary genpkg)+        ++ extractExecutableDeps (condExecutables genpkg)+        ++ extractTestSuiteDeps (condTestSuites genpkg)+        ++ extractBenchmarkDeps (condBenchmarks genpkg)+++sortNubByName :: [Dependency] -> [Dependency]+sortNubByName = fmap toDep . DM.toList . DM.fromList . fmap fromDep+  where+    fromDep (Dependency n v) = (n, v)+    toDep (n, v) = Dependency n v++filterPackageName :: PackageIdentifier -> [Dependency] -> [Dependency]+filterPackageName (PackageIdentifier pname _) =+  filter (\dep -> pname /= packageName dep )+  where+    packageName (Dependency pn _) = pn++dependencyName :: Dependency -> Text+dependencyName (Dependency (PackageName name) _) = T.pack name+++extractLibraryDeps :: Maybe (CondTree ConfVar [Dependency] Library) -> [Dependency]+extractLibraryDeps Nothing = []+extractLibraryDeps (Just x) = condTreeConstraints x++extractExecutableDeps :: [(String, CondTree ConfVar [Dependency] Executable)] -> [Dependency]+extractExecutableDeps = concatMap (condTreeConstraints . snd)++extractTestSuiteDeps :: [(String, CondTree ConfVar [Dependency] TestSuite)] -> [Dependency]+extractTestSuiteDeps = concatMap (condTreeConstraints . snd)++extractBenchmarkDeps :: [(String, CondTree ConfVar [Dependency] Benchmark)] -> [Dependency]+extractBenchmarkDeps = concatMap (condTreeConstraints . snd)
+ Jenga/HTTP.hs view
@@ -0,0 +1,35 @@+{-# LANGUAGE OverloadedStrings #-}+module Jenga.HTTP where++import Control.Monad (when, void)++import Data.Aeson (eitherDecode')++import Network.HTTP.Client.Conduit+import Network.HTTP.Types.Header (hAccept)+import Network.HTTP.Types.Status (status200)++import Jenga.PackageList+++newtype StackResolver+  = StackResolver String+  deriving (Eq, Show)++++stackageUrl :: String+stackageUrl = "https://www.stackage.org/"++-- Should use an ErrorT here.+getStackageResolverPkgList :: StackResolver -> IO (Either String PackageList)+getStackageResolverPkgList (StackResolver sr) = do+  -- TODO: swap out for something that doesn't `fail`.+  request <- parseRequest $ stackageUrl ++ sr+  withManager $ do+    response <- httpLbs $ request { requestHeaders = (hAccept, "application/json") : requestHeaders request }++    when (responseStatus response /= status200) $+      void . error $ "getStackageResolverPkgList: status " ++ show (responseStatus response)++    pure $ eitherDecode' (responseBody response)
+ Jenga/PackageList.hs view
@@ -0,0 +1,88 @@+{-# LANGUAGE OverloadedStrings #-}++module Jenga.PackageList+  ( PackageInfo (..)+  , PackageList (..)+  , lookupPackages+  ) where++import           Data.Aeson+import           Data.Aeson.Types (typeMismatch)++import           Data.Map.Strict (Map)+import qualified Data.Map.Strict as DM++import           Data.Text (Text)+++data PackageList = PackageList+  { ghcVersion :: Text+  , creatDate :: Text+  , resolverName :: Text+  , packageMap :: Map Text PackageInfo+  }+  deriving Show++data Package = Package+  { _pkgName :: Text+  , _pkgVer :: Text+  , _pkgSyn :: Text+  , _pkgCCore :: Bool+  }++data PackageInfo = PackageInfo+  { packageVersion :: Text+  , packageSynopsis :: Text+  , packageCore :: Bool+  }+  deriving Show++-- Temporary data type. Not exported.+data Snapshot = Snapshot+  { ghc :: Text+  , created :: Text+  , name :: Text+  }+++instance FromJSON Package where+  parseJSON (Object v) =+    Package <$> v .: "name"+            <*> v .: "version"+            <*> v .: "synopsis"+            <*> v .: "isCore"+  parseJSON invalid = typeMismatch "Package" invalid+++instance FromJSON Snapshot where+  parseJSON (Object v) =+    Snapshot <$> v .: "ghc"+              <*> v .: "created"+              <*> v .: "name"+  parseJSON invalid = typeMismatch "Snapshot" invalid++instance FromJSON PackageList where+  parseJSON (Object v) = do+    s <- v .: "snapshot"+    pkgs <- v .: "packages"+    pure $ PackageList (ghc s) (created s) (name s) $ mkPackageMap pkgs++  parseJSON invalid = typeMismatch "PackageList" invalid++mkPackageMap :: [Package] -> Map Text PackageInfo+mkPackageMap =+  DM.fromList . fmap convert+  where+    convert (Package nam ver syn core) =+      (nam, PackageInfo ver syn core)+++lookupPackages :: PackageList -> [Text] -> [Either Text (Text, PackageInfo)]+lookupPackages plist deps =+  fmap plookup deps+  where+    pmap = packageMap plist+    plookup k =+      case DM.lookup k pmap of+        Nothing -> Left k+        Just x -> Right (k, x)
+ Jenga/Stack.hs view
@@ -0,0 +1,28 @@+module Jenga.Stack+  ( readResolver+  ) where++-- Read the resolver from the stack.yaml file in the current directory.+-- This is unashamedly savage.++import           Data.Char (isSpace)+import qualified Data.List as DL++import           Jenga.HTTP+++readResolver :: IO (Maybe StackResolver)+readResolver = -- Is this the *only* file name?+  extract <$> readFile "stack.yaml"+++extract :: String -> Maybe StackResolver+extract xs =+  case filter (DL.isPrefixOf "resolver:") (lines xs) of+    [x] -> Just .StackResolver $ cleanup x+    _ -> Nothing+  where+    cleanup = DL.takeWhile (not . isSpace)+            . DL.dropWhile (isSpace)+            . DL.drop 1+            . DL.dropWhile (/= ':')
+ LICENSE view
@@ -0,0 +1,26 @@+Copyright (c) 2017, Erik de Castro Lopo+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are+met:++1. Redistributions of source code must retain the above copyright+   notice, this list of conditions and the following disclaimer.++2. 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.++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.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ jenga.cabal view
@@ -0,0 +1,49 @@+-- Initial jenga.cabal generated by cabal init.  For further documentation,+--  see http://haskell.org/cabal/users-guide/++name:                	jenga+version:             	0.1.0.0+synopsis:            	Generate a cabal freeze file from a stack.yaml+-- description:+homepage:            	https://github.com/erikd/jenga+license:             	BSD2+license-file:        	LICENSE+author:              	Erik de Castro Lopo+maintainer:          	erikd@mega-nerd.com+-- copyright:+category:            	Development+build-type:          	Simple+extra-source-files:  	ChangeLog.md+cabal-version:       	>= 1.10++library+  ghc-options:         	-Wall -fwarn-tabs+  default-language:    	Haskell2010++  build-depends:       	base					>= 4.8			&& < 4.10+                      , aeson                   == 1.1.*+                      , Cabal                   == 1.24.*+                      , containers              == 0.5.*+                      , bytestring              == 0.10.*+                      , http-conduit            == 2.2.*+                      , http-types              == 0.9.*+                      , text                    == 1.2.*+++  exposed-modules:      Jenga.Cabal+                      , Jenga.PackageList+                      , Jenga.HTTP+                      , Jenga.Stack++  other-extensions:     OverloadedStrings+++executable jenga+  ghc-options:         	-Wall -fwarn-tabs+  default-language:    	Haskell2010+  hs-source-dirs:       main+  main-is:              jenga.hs++  build-depends:       	base+                      , jenga+                      , text
+ main/jenga.hs view
@@ -0,0 +1,67 @@+{-# LANGUAGE OverloadedStrings #-}++import           Control.Monad (unless)++import           Data.Either (partitionEithers)+import qualified Data.List as DL+import           Data.Text (Text)+import qualified Data.Text.IO as T++import           Jenga.Cabal+import           Jenga.HTTP+import           Jenga.PackageList+import           Jenga.Stack++import           System.IO (hPutStrLn, stderr)+import           System.Environment (getArgs)+++main :: IO ()+main = do+  args <- getArgs+  case args of+    [cf] -> process cf+    _ -> usageExit+++usageExit :: IO ()+usageExit =+  putStrLn $ "\nUsage: jenga <cabal file>\n"++process :: FilePath -> IO ()+process cabalFile = do+  deps <- fmap dependencyName <$> readPackageDependencies cabalFile+  mr <- readResolver+  case mr of+    Nothing -> putStrLn "Not able to find resolver version in 'stack.yaml' file."+    Just r -> processResolver deps r++processResolver :: [Text] -> StackResolver -> IO ()+processResolver deps sr = do+  mpl <- getStackageResolverPkgList sr+  case mpl of+    Left s -> putStrLn $ "Error parse JSON: " ++ s+    Right pl -> processPackageList deps pl+++processPackageList :: [Text] -> PackageList -> IO ()+processPackageList deps plist = do+  let (missing, found) = partitionEithers $ lookupPackages plist deps+  unless (DL.null missing) $+    reportMissing missing+  printCabalFreeze found+++reportMissing :: [Text] -> IO ()+reportMissing [] = putStrLn "No missing packages found."+reportMissing xs =+  hPutStrLn stderr $ "The packages " ++ show xs ++ " could not be found in the specified stack resolver data."++printCabalFreeze :: [(Text, PackageInfo)] -> IO ()+printCabalFreeze xs = do+  putStr "constraints:"+  T.putStrLn . mconcat . DL.intersperse ",\n" $ DL.map render xs+  where+    render (name, pkg) =+      mconcat [ " ", name, " == ", packageVersion pkg ]+