packages feed

arch-hs 0.1.1.0 → 0.2.0.0

raw patch · 28 files changed

+1186/−425 lines, 28 filesnew-component:exe:arch-hs-submitPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

- Distribution.ArchHs.PkgDesc: [depends] :: PkgDesc -> [String]
- Distribution.ArchHs.PkgDesc: [desc] :: PkgDesc -> String
- Distribution.ArchHs.PkgDesc: [license] :: PkgDesc -> String
- Distribution.ArchHs.PkgDesc: [makeDepends] :: PkgDesc -> [String]
- Distribution.ArchHs.PkgDesc: [name] :: PkgDesc -> String
- Distribution.ArchHs.PkgDesc: [url] :: PkgDesc -> String
- Distribution.ArchHs.PkgDesc: [version] :: PkgDesc -> String
- Distribution.ArchHs.Types: CyclicError :: [PackageName] -> MyException
- Distribution.ArchHs.Types: PkgNotFound :: PackageName -> MyException
- Distribution.ArchHs.Types: TargetExist :: PackageName -> DependencyProvider -> MyException
- Distribution.ArchHs.Types: VersionError :: PackageName -> Version -> MyException
- Distribution.ArchHs.Types: data MyException
- Distribution.ArchHs.Types: instance GHC.Classes.Eq Distribution.ArchHs.Types.MyException
- Distribution.ArchHs.Types: instance GHC.Show.Show Distribution.ArchHs.Types.MyException
- Distribution.ArchHs.Types: type WithMyErr = Error MyException
- Distribution.ArchHs.Utils: fixName :: String -> String
- Distribution.ArchHs.Utils: toLower' :: String -> String
+ Distribution.ArchHs.Community: versionInCommunity :: (HasMyName n, Members [CommunityEnv, WithMyErr] r) => n -> Sem r CommunityVersion
+ Distribution.ArchHs.Exception: CyclicExist :: [PackageName] -> MyException
+ Distribution.ArchHs.Exception: NetworkException :: HttpException -> MyException
+ Distribution.ArchHs.Exception: PkgNotFound :: n -> MyException
+ Distribution.ArchHs.Exception: TargetExist :: PackageName -> DependencyProvider -> MyException
+ Distribution.ArchHs.Exception: VersionNotFound :: PackageName -> Version -> MyException
+ Distribution.ArchHs.Exception: data MyException
+ Distribution.ArchHs.Exception: instance GHC.Show.Show Distribution.ArchHs.Exception.MyException
+ Distribution.ArchHs.Exception: interceptHttpException :: Members [WithMyErr, Embed IO] r => IO a -> Sem r a
+ Distribution.ArchHs.Exception: printAppResult :: IO (Either MyException ()) -> IO ()
+ Distribution.ArchHs.Exception: printHandledIOException :: IO () -> IO ()
+ Distribution.ArchHs.Exception: type WithMyErr = Error MyException
+ Distribution.ArchHs.Internal.Prelude: (</>) :: FilePath -> FilePath -> FilePath
+ Distribution.ArchHs.Internal.Prelude: (<|>) :: Alternative f => f a -> f a -> f a
+ Distribution.ArchHs.Internal.Prelude: (\\) :: Eq a => [a] -> [a] -> [a]
+ Distribution.ArchHs.Internal.Prelude: class Generic a
+ Distribution.ArchHs.Internal.Prelude: class NFData a
+ Distribution.ArchHs.Internal.Prelude: decodeUtf8 :: ByteString -> Text
+ Distribution.ArchHs.Internal.Prelude: encodeUtf8 :: Text -> ByteString
+ Distribution.ArchHs.Internal.Prelude: groupBy :: (a -> a -> Bool) -> [a] -> [[a]]
+ Distribution.ArchHs.Internal.Prelude: infix 5 \\
+ Distribution.ArchHs.Internal.Prelude: infixl 3 <|>
+ Distribution.ArchHs.Internal.Prelude: infixr 5 </>
+ Distribution.ArchHs.Internal.Prelude: intercalate :: [a] -> [[a]] -> [a]
+ Distribution.ArchHs.Internal.Prelude: isInfixOf :: Eq a => [a] -> [a] -> Bool
+ Distribution.ArchHs.Internal.Prelude: isPrefixOf :: Eq a => [a] -> [a] -> Bool
+ Distribution.ArchHs.Internal.Prelude: nub :: Eq a => [a] -> [a]
+ Distribution.ArchHs.Internal.Prelude: prettyShow :: Pretty a => a -> String
+ Distribution.ArchHs.Internal.Prelude: simpleParsec :: Parsec a => String -> Maybe a
+ Distribution.ArchHs.Internal.Prelude: sort :: Ord a => [a] -> [a]
+ Distribution.ArchHs.Internal.Prelude: sortBy :: (a -> a -> Ordering) -> [a] -> [a]
+ Distribution.ArchHs.Internal.Prelude: splitOn :: Eq a => [a] -> [a] -> [[a]]
+ Distribution.ArchHs.Internal.Prelude: stripPrefix :: Eq a => [a] -> [a] -> Maybe [a]
+ Distribution.ArchHs.Internal.Prelude: type HasCallStack = ?callStack :: CallStack
+ Distribution.ArchHs.Internal.Prelude: when :: Applicative f => Bool -> f () -> f ()
+ Distribution.ArchHs.Name: CommunityRep :: NameRep
+ Distribution.ArchHs.Name: HackageRep :: NameRep
+ Distribution.ArchHs.Name: class HasMyName a
+ Distribution.ArchHs.Name: data MyName a
+ Distribution.ArchHs.Name: data NameRep
+ Distribution.ArchHs.Name: instance Distribution.ArchHs.Name.HasMyName (Distribution.ArchHs.Name.MyName 'Distribution.ArchHs.Name.CommunityRep)
+ Distribution.ArchHs.Name: instance Distribution.ArchHs.Name.HasMyName (Distribution.ArchHs.Name.MyName 'Distribution.ArchHs.Name.HackageRep)
+ Distribution.ArchHs.Name: instance Distribution.ArchHs.Name.HasMyName Distribution.ArchHs.Types.CommunityName
+ Distribution.ArchHs.Name: instance Distribution.ArchHs.Name.HasMyName Distribution.Types.PackageName.PackageName
+ Distribution.ArchHs.Name: instance forall k (a :: k). Control.DeepSeq.NFData (Distribution.ArchHs.Name.MyName a)
+ Distribution.ArchHs.Name: instance forall k (a :: k). Data.String.IsString (Distribution.ArchHs.Name.MyName a)
+ Distribution.ArchHs.Name: instance forall k (a :: k). GHC.Classes.Eq (Distribution.ArchHs.Name.MyName a)
+ Distribution.ArchHs.Name: instance forall k (a :: k). GHC.Classes.Ord (Distribution.ArchHs.Name.MyName a)
+ Distribution.ArchHs.Name: instance forall k (a :: k). GHC.Generics.Generic (Distribution.ArchHs.Name.MyName a)
+ Distribution.ArchHs.Name: instance forall k (a :: k). GHC.Read.Read (Distribution.ArchHs.Name.MyName a)
+ Distribution.ArchHs.Name: instance forall k (a :: k). GHC.Show.Show (Distribution.ArchHs.Name.MyName a)
+ Distribution.ArchHs.Name: isHaskellPackage :: CommunityName -> Bool
+ Distribution.ArchHs.Name: mToCommunityName :: MyName 'CommunityRep -> CommunityName
+ Distribution.ArchHs.Name: mToHackageName :: MyName 'HackageRep -> PackageName
+ Distribution.ArchHs.Name: toCommunityName :: HasMyName n => n -> CommunityName
+ Distribution.ArchHs.Name: toCommunityRep :: HasMyName a => a -> MyName 'CommunityRep
+ Distribution.ArchHs.Name: toHackageName :: HasMyName n => n -> PackageName
+ Distribution.ArchHs.Name: toHackageRep :: HasMyName a => a -> MyName 'HackageRep
+ Distribution.ArchHs.Name: unMyName :: MyName a -> String
+ Distribution.ArchHs.OptionReader: optExtraCabalReader :: ReadM [FilePath]
+ Distribution.ArchHs.OptionReader: optFlagReader :: ReadM (Map PackageName FlagAssignment)
+ Distribution.ArchHs.OptionReader: optPackageNameReader :: ReadM PackageName
+ Distribution.ArchHs.OptionReader: optSkippedReader :: ReadM [String]
+ Distribution.ArchHs.OptionReader: optVersionReader :: ReadM Version
+ Distribution.ArchHs.PP: prettyDeps :: [PackageName] -> String
+ Distribution.ArchHs.PP: prettyFlagAssignments :: Map PackageName FlagAssignment -> String
+ Distribution.ArchHs.PP: prettyFlags :: [(PackageName, [Flag])] -> String
+ Distribution.ArchHs.PP: prettySkip :: [String] -> String
+ Distribution.ArchHs.PP: prettySolvedPkgs :: [SolvedPackage] -> String
+ Distribution.ArchHs.PkgDesc: [_conflicts] :: PkgDesc -> [String]
+ Distribution.ArchHs.PkgDesc: [_depends] :: PkgDesc -> [String]
+ Distribution.ArchHs.PkgDesc: [_desc] :: PkgDesc -> String
+ Distribution.ArchHs.PkgDesc: [_license] :: PkgDesc -> Maybe String
+ Distribution.ArchHs.PkgDesc: [_makeDepends] :: PkgDesc -> [String]
+ Distribution.ArchHs.PkgDesc: [_name] :: PkgDesc -> String
+ Distribution.ArchHs.PkgDesc: [_optDepends] :: PkgDesc -> [String]
+ Distribution.ArchHs.PkgDesc: [_provides] :: PkgDesc -> [String]
+ Distribution.ArchHs.PkgDesc: [_replaces] :: PkgDesc -> [String]
+ Distribution.ArchHs.PkgDesc: [_url] :: PkgDesc -> Maybe String
+ Distribution.ArchHs.PkgDesc: [_version] :: PkgDesc -> String
+ Distribution.ArchHs.PkgDesc: instance Control.DeepSeq.NFData Distribution.ArchHs.PkgDesc.PkgDesc
+ Distribution.ArchHs.PkgDesc: instance GHC.Classes.Eq Distribution.ArchHs.PkgDesc.PkgDesc
+ Distribution.ArchHs.PkgDesc: instance GHC.Generics.Generic Distribution.ArchHs.PkgDesc.PkgDesc
+ Distribution.ArchHs.PkgDesc: instance GHC.Show.Show Distribution.ArchHs.PkgDesc.PkgDesc
+ Distribution.ArchHs.PkgDesc: runDescFieldsParser :: String -> String -> Either (ParseErrorBundle String Void) (Map String [String])
+ Distribution.ArchHs.Types: CommunityName :: String -> CommunityName
+ Distribution.ArchHs.Types: [unCommunityName] :: CommunityName -> String
+ Distribution.ArchHs.Types: instance Control.DeepSeq.NFData Distribution.ArchHs.Types.CommunityName
+ Distribution.ArchHs.Types: instance GHC.Classes.Eq Distribution.ArchHs.Types.CommunityName
+ Distribution.ArchHs.Types: instance GHC.Classes.Ord Distribution.ArchHs.Types.CommunityName
+ Distribution.ArchHs.Types: instance GHC.Generics.Generic Distribution.ArchHs.Types.CommunityName
+ Distribution.ArchHs.Types: instance GHC.Read.Read Distribution.ArchHs.Types.CommunityName
+ Distribution.ArchHs.Types: instance GHC.Show.Show Distribution.ArchHs.Types.CommunityName
+ Distribution.ArchHs.Types: newtype CommunityName
+ Distribution.ArchHs.Types: type CommunityVersion = String
- Distribution.ArchHs.Aur: aurToIO :: Member (Embed IO) r => Sem (Aur : r) a -> Sem r a
+ Distribution.ArchHs.Aur: aurToIO :: Members [WithMyErr, Embed IO] r => Sem (Aur : r) a -> Sem r a
- Distribution.ArchHs.Aur: isInAur :: Member Aur r => PackageName -> Sem r Bool
+ Distribution.ArchHs.Aur: isInAur :: (HasMyName n, Member Aur r) => n -> Sem r Bool
- Distribution.ArchHs.Community: isInCommunity :: Member CommunityEnv r => PackageName -> Sem r Bool
+ Distribution.ArchHs.Community: isInCommunity :: (HasMyName n, Member CommunityEnv r) => n -> Sem r Bool
- Distribution.ArchHs.PkgDesc: PkgDesc :: String -> String -> String -> String -> String -> [String] -> [String] -> PkgDesc
+ Distribution.ArchHs.PkgDesc: PkgDesc :: String -> String -> String -> Maybe String -> Maybe String -> [String] -> [String] -> [String] -> [String] -> [String] -> [String] -> PkgDesc
- Distribution.ArchHs.PkgDesc: runDescParser :: String -> Either (ParseErrorBundle String Void) PkgDesc
+ Distribution.ArchHs.PkgDesc: runDescParser :: String -> String -> Either (ParseErrorBundle String Void) PkgDesc
- Distribution.ArchHs.Types: type CommunityDB = Set String
+ Distribution.ArchHs.Types: type CommunityDB = Map CommunityName CommunityVersion

Files

CHANGELOG.md view
@@ -3,6 +3,18 @@ `arch-hs` uses [PVP Versioning][1]. The changelog is available [on GitHub][2]. +## 0.2.0.0++* More accurate naming conversion between hackage representation and archlinux representation, according to [NAME_PRESET.json](https://github.com/berberman/arch-hs/blob/master/data/NAME_PRESET.json)++* Clearer project structure++* More reasonable exceptions++* Provide versions of haskell packages in archlinux community++* Add `arch-hs-submit` executable+ ## 0.1.1.0  * Add uusi option
README.md view
@@ -91,7 +91,7 @@ ```  This message tells us that in order to package `accelerate`, we must package `unique`-and `tasty-kat` first sequentially, because `accelerate` dependens on them to build or test,+and `tasty-kat` first sequentially, because `accelerate` dependents on them to build or test, whereas they are not present in archlinux community repo.  ```@@ -111,9 +111,6 @@ # This file was generated by arch-hs, please check it manually. # Maintainer: Your Name <youremail@domain.com> -# This file was generated by arch-hs, please check it manually.-# Maintainer: Your Name <youremail@domain.com>- _hkgname=accelerate pkgname=haskell-accelerate pkgver=1.3.0.0@@ -279,6 +276,21 @@  For all available options, have a look at the help message. ++## [Name preset](https://github.com/berberman/arch-hs/blob/master/data/NAME_PRESET.json)++To distribute a haskell package to archlinux, the name of package should be changed according to the naming convention:++* for haskell libraries, their names must have `haskell-` prefix++* for programs, it depends on circumstances++* names should always be in lower case++However, it's not enough to prefix the string with `haskell-` and transform to lower case; in some special situations, the hackage name+may have `haskell-` prefix already, or the case is irregular, thus we have to a name preset manually. Once a package distributed to archlinux,+whose name conform to above-mentioned situation, the name preset should be upgraded correspondingly.+ ## Diff  `arch-hs` also provides a component called `arch-hs-diff`. `arch-hs-diff` can show the differences of package description between two versions of a package.@@ -355,6 +367,10 @@  `arch-hs-diff` does not require hackage db, it downloads cabal files from hackage server instead.  +## Submit++For hackage distribution maintainers only.+ ## Limitations  * The dependency solver will **ONLY** expand the dependencies of *executables* , *libraries* and *sub-libraries* recursively, because@@ -368,11 +384,7 @@  - [ ] **Standardized pretty printing**. -- [ ] StructuralPKGBUILD template.- - [x] AUR support.--- [ ] A watchdog during dependency calculation.  - [x] Working with given `.cabal` files which haven't been released to hackage. 
app/Args.hs view
@@ -8,9 +8,9 @@ where  import qualified Data.Map.Strict as Map-import Distribution.ArchHs.Types (FlagAssignments)-import Distribution.Types.PackageName (PackageName)-import OptionParse+import Distribution.ArchHs.Internal.Prelude+import Distribution.ArchHs.OptionReader+import Distribution.ArchHs.Types  data Options = Options   { optHackagePath :: FilePath,
app/Main.hs view
@@ -10,10 +10,8 @@ import Args import qualified Colourista as C import Conduit-import qualified Control.Exception as CE-import Control.Monad (filterM, when)+import Control.Monad (filterM) import Data.IORef (IORef, newIORef)-import Data.List (groupBy, intercalate, isInfixOf, nub) import Data.List.NonEmpty (toList) import qualified Data.Map.Strict as Map import qualified Data.Set as Set@@ -28,6 +26,7 @@   ( cabalToPkgBuild,     getDependencies,   )+import Distribution.ArchHs.Exception import Distribution.ArchHs.Hackage   ( getPackageFlag,     insertDB,@@ -35,6 +34,7 @@     lookupHackagePath,     parseCabalFile,   )+import Distribution.ArchHs.Internal.Prelude import Distribution.ArchHs.Local import Distribution.ArchHs.PP   ( prettyDeps,@@ -47,14 +47,11 @@ import Distribution.ArchHs.Types import Distribution.ArchHs.Utils (getTwo) import Distribution.Hackage.DB (HackageDB)-import Distribution.PackageDescription (FlagAssignment)-import Distribution.Types.PackageName-  ( PackageName,-    unPackageName,+import System.Directory+  ( createDirectoryIfMissing,+    doesFileExist,   )-import Distribution.Types.UnqualComponentName (mkUnqualComponentName)-import System.Directory (createDirectoryIfMissing, doesFileExist, removeFile)-import System.FilePath (takeFileName, (</>))+import System.FilePath (takeFileName)  app ::   Members '[Embed IO, State (Set.Set PackageName), CommunityEnv, HackageEnv, FlagAssignmentsEnv, DependencyRecord, Trace, Aur, WithMyErr] r =>@@ -62,9 +59,9 @@   FilePath ->   Bool ->   [String] ->-  Bool->+  Bool ->   Sem r ()-app target path aurSupport skip uusi= do+app target path aurSupport skip uusi = do   (deps, ignored) <- getDependencies (fmap mkUnqualComponentName skip) Nothing target   inCommunity <- isInCommunity target   when inCommunity $ throw $ TargetExist target ByCommunity@@ -114,7 +111,7 @@       removeSelfCycle g = foldr (\n acc -> GL.removeEdge n n acc) g $ toBePacked2 ^.. each . pkgName       newGraph = GL.induce (`notElem` vertexesToBeRemoved) deps   flattened <- case G.topSort . GL.skeleton $ removeSelfCycle $ newGraph of-    Left c -> throw . CyclicError $ toList c+    Left c -> throw . CyclicExist $ toList c     Right x -> return x   embed $ putStrLn . prettyDeps . reverse $ flattened   flags <- filter (\(_, l) -> length l /= 0) <$> mapM (\n -> (n,) <$> getPackageFlag n) flattened@@ -172,64 +169,59 @@ -----------------------------------------------------------------------------  main :: IO ()-main = CE.catch @CE.IOException-  ( do-      Options {..} <- runArgsParser+main = printHandledIOException $+  do+    Options {..} <- runArgsParser -      let traceToFile = not $ null optFileTrace-      when (traceToFile) $ do-        C.infoMessage $ "Trace will be write to " <> (T.pack optFileTrace) <> "."-        exist <- doesFileExist optFileTrace-        when exist $ do-          C.warningMessage $ "File " <> (T.pack optFileTrace) <> " already existed, delete it."-          removeFile optFileTrace+    let traceToFile = not $ null optFileTrace+    when (traceToFile) $ do+      C.infoMessage $ "Trace will be dumped to " <> (T.pack optFileTrace) <> "."+      exist <- doesFileExist optFileTrace+      when exist $+        C.warningMessage $ "File " <> (T.pack optFileTrace) <> " already existed, overwrite it." -      let useDefaultHackage = isInfixOf "YOUR_HACKAGE_MIRROR" $ optHackagePath-          useDefaultCommunity = "/var/lib/pacman/sync/community.db" == optCommunityPath+    let useDefaultHackage = isInfixOf "YOUR_HACKAGE_MIRROR" $ optHackagePath+        useDefaultCommunity = "/var/lib/pacman/sync/community.db" == optCommunityPath -      when useDefaultHackage $ C.skipMessage "You didn't pass -h, use hackage index file from default path."-      when useDefaultCommunity $ C.skipMessage "You didn't pass -c, use community db file from default path."+    when useDefaultHackage $ C.skipMessage "You didn't pass -h, use hackage index file from default path."+    when useDefaultCommunity $ C.skipMessage "You didn't pass -c, use community db file from default path." -      let isFlagEmpty = optFlags == Map.empty-          isSkipEmpty = optSkip == []+    let isFlagEmpty = optFlags == Map.empty+        isSkipEmpty = optSkip == [] -      when isFlagEmpty $ C.skipMessage "You didn't pass -f, different flag assignments may make difference in dependency resolving."-      when (not isFlagEmpty) $ do-        C.infoMessage "You assigned flags:"-        putStrLn . prettyFlagAssignments $ optFlags+    when isFlagEmpty $ C.skipMessage "You didn't pass -f, different flag assignments may make difference in dependency resolving."+    when (not isFlagEmpty) $ do+      C.infoMessage "You assigned flags:"+      putStrLn . prettyFlagAssignments $ optFlags -      when (not isSkipEmpty) $ do-        C.infoMessage "You chose to skip:"-        putStrLn $ prettySkip optSkip+    when (not isSkipEmpty) $ do+      C.infoMessage "You chose to skip:"+      putStrLn $ prettySkip optSkip -      when optAur $ C.infoMessage "You passed -a, searching AUR may takes a long time."+    when optAur $ C.infoMessage "You passed -a, searching AUR may takes a long time." -      when optUusi $ C.infoMessage "You passed --uusi, uusi will become makedepends of each package."+    when optUusi $ C.infoMessage "You passed --uusi, uusi will become makedepends of each package." -      hackage <- loadHackageDB =<< if useDefaultHackage then lookupHackagePath else return optHackagePath-      C.infoMessage "Loading hackage..."+    hackage <- loadHackageDB =<< if useDefaultHackage then lookupHackagePath else return optHackagePath+    C.infoMessage "Loading hackage..." -      let isExtraEmpty = optExtraCabalPath == []+    let isExtraEmpty = optExtraCabalPath == [] -      when (not isExtraEmpty) $-        C.infoMessage $ "You added " <> (T.pack . intercalate ", " $ map takeFileName optExtraCabalPath) <> " as extra cabal file(s), starting parsing right now."+    when (not isExtraEmpty) $+      C.infoMessage $ "You added " <> (T.pack . intercalate ", " $ map takeFileName optExtraCabalPath) <> " as extra cabal file(s), starting parsing right now." -      parsedExtra <- mapM parseCabalFile optExtraCabalPath+    parsedExtra <- mapM parseCabalFile optExtraCabalPath -      let newHackage = foldr (\x acc -> x `insertDB` acc) hackage parsedExtra+    let newHackage = foldr (\x acc -> x `insertDB` acc) hackage parsedExtra -      community <- loadProcessedCommunity $ if useDefaultCommunity then defaultCommunityPath else optCommunityPath-      C.infoMessage "Loading community.db..."+    community <- loadProcessedCommunity $ if useDefaultCommunity then defaultCommunityPath else optCommunityPath+    C.infoMessage "Loading community.db..." -      C.infoMessage "Start running..."+    C.infoMessage "Start running..." -      empty <- newIORef Set.empty+    empty <- newIORef Set.empty -      runApp newHackage community optFlags optStdoutTrace optFileTrace empty (app optTarget optOutputDir optAur optSkip optUusi) >>= \case-        Left x -> C.errorMessage $ "Runtime Error: " <> (T.pack . show $ x)-        _ -> C.successMessage "Success!"-  )-  $ \e -> C.errorMessage $ "IOException: " <> (T.pack . show $ e)+    runApp newHackage community optFlags optStdoutTrace optFileTrace empty (app optTarget optOutputDir optAur optSkip optUusi) & printAppResult  ----------------------------------------------------------------------------- 
arch-hs.cabal view
@@ -1,7 +1,7 @@ cabal-version:       2.4 name:                arch-hs-version:             0.1.1.0-synopsis:            A program generating PKGBUILD for hackage packages+version:             0.2.0.0+synopsis:            Distribute hackage packages to archlinux description:              @arch-hs@ is a command-line program, which simplifies the process of producing     and maintaining haskell packages for archlinux distribution by automating the@@ -17,6 +17,7 @@ copyright:           2020 berberman category:            Distribution build-type:          Simple+extra-source-files:  data/NAME_PRESET.json extra-doc-files:     README.md                      CHANGELOG.md tested-with:         GHC == 8.10.2@@ -72,7 +73,6 @@                        PolyKinds                        RankNTypes                        ScopedTypeVariables-                       TypeApplications                        TypeOperators                        TypeFamilies library@@ -88,9 +88,13 @@                        Distribution.ArchHs.Types,                        Distribution.ArchHs.Utils,                        Distribution.ArchHs.PP,-                       OptionParse,-                       Data.Aeson.Ext,-                       Polysemy.Req+                       Distribution.ArchHs.Name,+                       Distribution.ArchHs.Exception,+                       Distribution.ArchHs.Internal.Prelude+                       Distribution.ArchHs.OptionReader+  other-modules:       Data.Aeson.Ext+                       Distribution.ArchHs.Internal.NamePresetLoader+                         executable arch-hs   import:              common-options@@ -107,6 +111,16 @@   hs-source-dirs:      diff   main-is:             Main.hs   other-modules:       Diff+  build-depends:       arch-hs+  ghc-options:         -threaded+                       -rtsopts+                       -with-rtsopts=-N+                       +executable arch-hs-submit+  import:              common-options+  hs-source-dirs:      submit+  main-is:             Main.hs+  other-modules:       Submit   build-depends:       arch-hs   ghc-options:         -threaded                        -rtsopts
+ data/NAME_PRESET.json view
@@ -0,0 +1,92 @@+{+  "falseList": [+    "haskell-network2.8",+    "haskell-sbv8.7"+  ],+  "preset": {+    "agda": "Agda",+    "alex": "alex",+    "arch-hs": "arch-hs",+    "c2hs": "c2hs",+    "haskell-cabal": "Cabal",+    "cabal-install": "cabal-install",+    "cgrep": "cgrep",+    "cryptol": "cryptol",+    "darcs": "darcs",+    "dhall": "dhall",+    "dhall-bash": "dhall-bash",+    "dhall-json": "dhall-json",+    "dhall-lsp-server": "dhall-lsp-server",+    "dhall-yaml": "dhall-yaml",+    "git-annex": "git-annex",+    "git-repair": "git-repair",+    "happy": "happy",+    "haskell-chasingbottoms": "ChasingBottoms",+    "haskell-ci": "haskell-ci",+    "haskell-configfile": "ConfigFile",+    "haskell-cracknum": "crackNum",+    "haskell-dav": "DAV",+    "haskell-decimal": "Decimal",+    "haskell-diff": "Diff",+    "haskell-edisonapi": "EdisonAPI",+    "haskell-edisoncore": "EdisonCore",+    "haskell-findbin": "FindBin",+    "haskell-floatinghex": "FloatingHex",+    "haskell-gi": "haskell-gi",+    "haskell-glob": "Glob",+    "haskell-gtk": "gtk3",+    "haskell-graphscc": "GraphSCC",+    "haskell-hopenpgp": "hOpenPGP",+    "haskell-http": "HTTP",+    "haskell-hunit": "HUnit",+    "haskell-ifelse": "IfElse",+    "haskell-juicypixels": "JuicyPixels",+    "haskell-lexer": "haskell-lexer",+    "haskell-listlike": "ListLike",+    "haskell-missingh": "MissingH",+    "haskell-monadlib": "monadLib",+    "haskell-monadrandom": "MonadRandom",+    "haskell-only": "Only",+    "haskell-puremd5": "pureMD5",+    "haskell-quickcheck": "QuickCheck",+    "haskell-ranged-sets": "Ranged-sets",+    "haskell-safesemaphore": "SafeSemaphore",+    "haskell-sbv8.7": "haskell-sbv8.7",+    "haskell-sha": "SHA",+    "haskell-smtlib": "smtLib",+    "haskell-src-exts": "haskell-src-exts",+    "haskell-src-exts-util": "haskell-src-exts-util",+    "haskell-src-meta": "haskell-src-meta",+    "haskell-statevar": "StateVar",+    "haskell-stmonadtrans": "STMonadTrans",+    "haskell-unixutils": "Unixutils",+    "haskell-x11": "X11",+    "haskell-x11-xft": "X11-xft",+    "hasktags": "hasktags",+    "hledger": "hledger",+    "hledger-api": "hledger-api",+    "hledger-ui": "hledger-ui",+    "hledger-web": "hledger-web",+    "hlint": "hlint",+    "hoogle": "hoogle",+    "hopenpgp-tools": "hopenpgp-tools",+    "idris": "idris",+    "misfortune": "misfortune",+    "pandoc": "pandoc",+    "pandoc-citeproc": "pandoc-citeproc",+    "pandoc-crossref": "pandoc-crossref",+    "postgrest": "postgrest",+    "shellcheck": "ShellCheck",+    "stack": "stack",+    "stylish-haskell": "stylish-haskell",+    "tamarin-prover": "tamarin-prover",+    "taskell": "taskell",+    "tidalcycles": "tidal",+    "unlambda": "unlambda",+    "uusi": "uusi",+    "xmobar": "xmobar",+    "xmonad": "xmonad",+    "xmonad-contrib": "xmonad-contrib",+    "xmonad-utils": "xmonad-utils"+  }+}
diff/Diff.hs view
@@ -1,4 +1,6 @@ {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE TypeApplications #-}  module Diff   ( diffCabal,@@ -8,28 +10,27 @@ where  import qualified Colourista as C-import qualified Control.Exception as CE-import Data.List (intercalate, nub, sort, (\\)) import qualified Data.Map.Strict as Map+import Data.Maybe (fromJust) import qualified Data.Text as T-import Distribution.ArchHs.Core+import Distribution.ArchHs.Community (versionInCommunity)+import Distribution.ArchHs.Core (evalConditionTree)+import Distribution.ArchHs.Exception+import Distribution.ArchHs.Internal.Prelude+import Distribution.ArchHs.OptionReader import Distribution.ArchHs.PP (prettyFlags) import Distribution.ArchHs.Types import Distribution.ArchHs.Utils-import Distribution.PackageDescription+import Distribution.PackageDescription (CondTree, ConfVar) import Distribution.PackageDescription.Parsec (parseGenericPackageDescriptionMaybe)-import Distribution.Pretty (prettyShow) import qualified Distribution.Types.BuildInfo.Lens as L-import Distribution.Types.Dependency-import Distribution.Types.PackageName-import Distribution.Types.UnqualComponentName+import Distribution.Types.Dependency (Dependency) import Distribution.Utils.ShortText (fromShortText)-import Distribution.Version import Network.HTTP.Req hiding (header)-import OptionParse  data Options = Options-  { optFlags :: FlagAssignments,+  { optCommunityPath :: FilePath,+    optFlags :: FlagAssignments,     optPackageName :: PackageName,     optVersionA :: Version,     optVersionB :: Version@@ -38,7 +39,15 @@ cmdOptions :: Parser Options cmdOptions =   Options-    <$> option+    <$> strOption+      ( long "community"+          <> metavar "PATH"+          <> short 'c'+          <> help "Path to community.db"+          <> showDefault+          <> value "/var/lib/pacman/sync/community.db"+      )+    <*> option       optFlagReader       ( long "flags"           <> metavar "package_name:flag_name:true|false,..."@@ -68,18 +77,20 @@  type VersionedComponentList = [(UnqualComponentName, VersionedList)] -collectLibDeps :: Members [FlagAssignmentsEnv, Trace] r => GenericPackageDescription -> Sem r (VersionedList, VersionedList)+collectLibDeps :: Members [FlagAssignmentsEnv, Trace, DependencyRecord] r => GenericPackageDescription -> Sem r (VersionedList, VersionedList) collectLibDeps cabal = do   case cabal & condLibrary of     Just lib -> do       bInfo <- evalConditionTree cabal lib       let libDeps = fmap unDepV $ buildDependsIfBuild bInfo           toolDeps = fmap unExeV $ buildToolDependsIfBuild bInfo+      mapM_ (uncurry updateDependencyRecord) libDeps+      mapM_ (uncurry updateDependencyRecord) toolDeps       return (libDeps, toolDeps)     Nothing -> return ([], [])  collectRunnableDeps ::-  (Semigroup k, L.HasBuildInfo k, Members [FlagAssignmentsEnv, Trace] r) =>+  (Semigroup k, L.HasBuildInfo k, Members [FlagAssignmentsEnv, Trace, DependencyRecord] r) =>   (GenericPackageDescription -> [(UnqualComponentName, CondTree ConfVar [Dependency] k)]) ->   GenericPackageDescription ->   [UnqualComponentName] ->@@ -87,33 +98,58 @@ collectRunnableDeps f cabal skip = do   let exes = cabal & f   bInfo <- filter (not . (`elem` skip) . fst) . zip (exes <&> fst) <$> mapM (evalConditionTree cabal . snd) exes-  let runnableDeps = bInfo <&> ((_2 %~) $ fmap unDepV . buildDependsIfBuild)+  let deps = bInfo <&> ((_2 %~) $ fmap unDepV . buildDependsIfBuild)       toolDeps = bInfo <&> ((_2 %~) $ fmap unExeV . buildToolDependsIfBuild)-  return (runnableDeps, toolDeps)+  mapM_ (uncurry updateDependencyRecord) $ deps ^.. each . _2 ^. each+  mapM_ (uncurry updateDependencyRecord) $ toolDeps ^.. each . _2 ^. each+  return (deps, toolDeps) -collectExeDeps :: Members [FlagAssignmentsEnv, Trace] r => GenericPackageDescription -> [UnqualComponentName] -> Sem r (VersionedComponentList, VersionedComponentList)+collectExeDeps :: Members [FlagAssignmentsEnv, Trace, DependencyRecord] r => GenericPackageDescription -> [UnqualComponentName] -> Sem r (VersionedComponentList, VersionedComponentList) collectExeDeps = collectRunnableDeps condExecutables -collectTestDeps :: Members [FlagAssignmentsEnv, Trace] r => GenericPackageDescription -> [UnqualComponentName] -> Sem r (VersionedComponentList, VersionedComponentList)+collectTestDeps :: Members [FlagAssignmentsEnv, Trace, DependencyRecord] r => GenericPackageDescription -> [UnqualComponentName] -> Sem r (VersionedComponentList, VersionedComponentList) collectTestDeps = collectRunnableDeps condTestSuites +updateDependencyRecord :: Member DependencyRecord r => PackageName -> VersionRange -> Sem r ()+updateDependencyRecord name range = modify' $ Map.insertWith (<>) name [range]++-----------------------------------------------------------------------------+ getCabalFromHackage :: Members [Embed IO, WithMyErr] r => PackageName -> Version -> Sem r GenericPackageDescription getCabalFromHackage name version = do   let urlPath = T.pack $ unPackageName name <> "-" <> prettyShow version       api = https "hackage.haskell.org" /: "package" /: urlPath /: "revision" /: "0.cabal"       r = req GET api NoReqBody bsResponse mempty   embed $ C.infoMessage $ "Downloading cabal file from " <> renderUrl api <> "..."-  response <- embed $ CE.try @HttpException (runReq defaultHttpConfig r)-  result <- case response of-    Left _ -> throw $ VersionError name version-    Right x -> return x-  case parseGenericPackageDescriptionMaybe $ responseBody result of+  response <- interceptHttpException (runReq defaultHttpConfig r)+  case parseGenericPackageDescriptionMaybe $ responseBody response of     Just x -> return x-    _ -> embed @IO $ fail $ "Failed to parse .cabal file from " <> show api+    _ -> error $ "Failed to parse .cabal file from " <> show api +directDependencies ::+  Members [FlagAssignmentsEnv, Trace, DependencyRecord] r =>+  GenericPackageDescription ->+  Sem r (VersionedList, VersionedList)+directDependencies cabal = do+  (libDeps, libToolsDeps) <- collectLibDeps cabal+  (exeDeps, exeToolsDeps) <- collectExeDeps cabal []+  (testDeps, testToolsDeps) <- collectTestDeps cabal []+  let flatten = mconcat . fmap snd+      l = libDeps+      lt = libToolsDeps+      e = flatten exeDeps+      et = flatten exeToolsDeps+      t = flatten testDeps+      tt = flatten testToolsDeps+      notMyself = (/= (getPkgName' cabal))+      distinct = filter (notMyself . fst) . nub+      depends = distinct $ l <> e+      makedepends = (distinct $ lt <> et <> t <> tt) \\ depends+  return (depends, makedepends)+ ----------------------------------------------------------------------------- -diffCabal :: Members [FlagAssignmentsEnv, WithMyErr, Trace, Embed IO] r => PackageName -> Version -> Version -> Sem r String+diffCabal :: Members [CommunityEnv, FlagAssignmentsEnv, WithMyErr, Trace, DependencyRecord, Embed IO] r => PackageName -> Version -> Version -> Sem r String diffCabal name a b = do   ga <- getCabalFromHackage name a   gb <- getCabalFromHackage name b@@ -123,6 +159,8 @@       fb = genPackageFlags ga   (ba, ma) <- directDependencies ga   (bb, mb) <- directDependencies gb+  queryb <- lookupDiffCommunity ba bb+  querym <- lookupDiffCommunity ma mb   return $     unlines       [ C.formatWith [C.magenta] "Package: " <> unPackageName name,@@ -130,35 +168,18 @@         desc pa pb,         url pa pb,         dep "Depends: \n" ba bb,+        "",+        queryb,         dep "MakeDepends: \n" ma mb,+        "",+        querym,         flags name fa fb       ] -directDependencies ::-  Members [FlagAssignmentsEnv, Trace] r =>-  GenericPackageDescription ->-  Sem r ([String], [String])-directDependencies cabal = do-  (libDeps, libToolsDeps) <- collectLibDeps cabal-  (exeDeps, exeToolsDeps) <- collectExeDeps cabal []-  (testDeps, testToolsDeps) <- collectTestDeps cabal []-  let connectVersionWithName (n, range) = unPackageName n <> "  " <> prettyShow range-      flatten = mconcat . fmap snd-      l = libDeps-      lt = libToolsDeps-      e = flatten exeDeps-      et = flatten exeToolsDeps-      t = flatten testDeps-      tt = flatten testToolsDeps-      notMyself = (/= (getPkgName' cabal))-      distinct = filter (notMyself . fst) . nub-      depends = distinct $ l <> e-      makedepends = (distinct $ lt <> et <> t <> tt) \\ depends-  return (fmap connectVersionWithName depends, fmap connectVersionWithName makedepends)- diffTerm :: String -> (a -> String) -> a -> a -> String diffTerm s f a b =-  let (ra, rb) = (f a, f b)+  let f' = T.unpack . T.strip . T.pack . f+      (ra, rb) = (f' a, f' b)    in (C.formatWith [C.magenta] s)         <> (if ra == rb then ra else ((C.formatWith [C.red] ra) <> "  ⇒  " <> C.formatWith [C.green] rb)) @@ -171,22 +192,61 @@ url :: PackageDescription -> PackageDescription -> String url = diffTerm "URL: " getUrl -dep :: String -> [String] -> [String] -> String-dep s a b =+splitLine :: String+splitLine = "\n" <> replicate 38 '-' <> "\n"++inRange :: Members [CommunityEnv, WithMyErr] r => (PackageName, VersionRange) -> Sem r (Either (PackageName, VersionRange) (PackageName, VersionRange, Version, Bool))+inRange (name, hRange) =+  (try @MyException (versionInCommunity name))+    >>= \case+      Right y -> let version = fromJust . simpleParsec $ y in return . Right $ (name, hRange, version, withinRange version hRange)+      Left _ -> return . Left $ (name, hRange)++lookupDiffCommunity :: Members [CommunityEnv, WithMyErr] r => VersionedList -> VersionedList -> Sem r String+lookupDiffCommunity va vb = do+  let diffNew = vb \\ va+      diffOld = va \\ vb+      color b = C.formatWith [if b then C.green else C.red]+      pp b (Right (name, range, v, False)) =+        "["+          <> color b (unPackageName name)+          <> "] is required to be in range ("+          <> color b (prettyShow range)+          <> "), "+          <> "but community provides ("+          <> color b (prettyShow v)+          <> ")."+      pp _ (Right _) = ""+      pp b (Left (name, range)) =+        "["+          <> color b (unPackageName name)+          <> "] is required to be in range ("+          <> color b (prettyShow range)+          <> "), "+          <> "but community does not provide this package."++  new <- fmap (pp True) <$> mapM inRange diffNew+  old <- fmap (pp False) <$> mapM inRange diffOld+  let join = unlines . filter (not . null)+  return $ join old <> join new++dep :: String -> VersionedList -> VersionedList -> String+dep s va vb =   (C.formatWith [C.magenta] s) <> "    " <> case (diffOld <> diffNew) of     [] -> joinToString a     _ ->       (joinToString $ fmap (\x -> red (x `elem` diffOld) x) a)-        <> "\n"-        <> replicate 38 '-'-        <> "\n"+        <> splitLine         <> "    "         <> (joinToString $ fmap (\x -> green (x `elem` diffNew) x) b)   where+    a = joinVersionWithName <$> va+    b = joinVersionWithName <$> vb     diffNew = b \\ a     diffOld = a \\ b     joinToString [] = "[]"     joinToString xs = intercalate "\n    " $ sort xs+    joinVersionWithName (n, range) = unPackageName n <> "  " <> prettyShow range     red p x = if p then C.formatWith [C.red] x else x     green p x = if p then C.formatWith [C.green] x else x @@ -196,9 +256,7 @@     [] -> joinToString a     _ ->       (joinToString a)-        <> "\n"-        <> replicate 38 '-'-        <> "\n"+        <> splitLine         <> "    "         <> (joinToString b)   where
diff/Main.hs view
@@ -4,31 +4,39 @@ module Main (main) where  import qualified Colourista as C-import qualified Control.Exception as CE-import Control.Monad (when) import qualified Data.Map as Map import qualified Data.Text as T import Diff+import Distribution.ArchHs.Community+  ( defaultCommunityPath,+    loadProcessedCommunity,+  )+import Distribution.ArchHs.Exception+import Distribution.ArchHs.Internal.Prelude import Distribution.ArchHs.PP (prettyFlagAssignments) import Distribution.ArchHs.Types  main :: IO ()-main = CE.catch @CE.IOException-  ( do-      Options {..} <- runArgsParser+main = printHandledIOException $+  do+    Options {..} <- runArgsParser+    let useDefaultCommunity = "/var/lib/pacman/sync/community.db" == optCommunityPath+        isFlagEmpty = Map.null optFlags -      let isFlagEmpty = Map.null optFlags-      when isFlagEmpty $ C.skipMessage "You didn't pass -f, different flag values may make difference in dependency resolving."-      when (not isFlagEmpty) $ do-        C.infoMessage "You assigned flags:"-        putStrLn . prettyFlagAssignments $ optFlags+    when useDefaultCommunity $ C.skipMessage "You didn't pass -c, use community db file from default path." -      C.infoMessage "Start running..."-      runDiff optFlags (diffCabal optPackageName optVersionA optVersionB) >>= \case-        Left x -> C.errorMessage $ "Runtime Error: " <> (T.pack . show $ x)-        Right r -> putStrLn r >> C.successMessage "Success!"-  )-  $ \e -> C.errorMessage $ "IOException: " <> (T.pack . show $ e)+    when isFlagEmpty $ C.skipMessage "You didn't pass -f, different flag values may make difference in dependency resolving."+    when (not isFlagEmpty) $ do+      C.infoMessage "You assigned flags:"+      putStrLn . prettyFlagAssignments $ optFlags -runDiff :: FlagAssignments -> Sem '[FlagAssignmentsEnv, Trace, WithMyErr, Embed IO, Final IO] a -> IO (Either MyException a)-runDiff flags = runFinal . embedToFinal . errorToIOFinal . ignoreTrace . (runReader flags)+    community <- loadProcessedCommunity $ if useDefaultCommunity then defaultCommunityPath else optCommunityPath+    C.infoMessage "Loading community.db..."++    C.infoMessage "Start running..."+    runDiff community optFlags (diffCabal optPackageName optVersionA optVersionB) >>= \case+      Left x -> C.errorMessage $ "Runtime Error: " <> (T.pack . show $ x)+      Right r -> putStrLn r >> C.successMessage "Success!"++runDiff :: CommunityDB -> FlagAssignments -> Sem '[CommunityEnv, FlagAssignmentsEnv, Trace, DependencyRecord, WithMyErr, Embed IO, Final IO] a -> IO (Either MyException a)+runDiff community flags = runFinal . embedToFinal . errorToIOFinal . evalState (Map.empty) . ignoreTrace . (runReader flags) . (runReader community)
src/Data/Aeson/Ext.hs view
src/Distribution/ArchHs/Aur.hs view
@@ -24,12 +24,11 @@ import Data.Aeson import Data.Aeson.Ext (generateJSONInstance) import Data.Text (Text, pack)-import Distribution.ArchHs.Utils (fixName)-import Distribution.Types.PackageName (PackageName, unPackageName)-import GHC.Generics (Generic)+import Distribution.ArchHs.Exception+import Distribution.ArchHs.Internal.Prelude+import Distribution.ArchHs.Name+import Distribution.ArchHs.Types import Network.HTTP.Req-import Polysemy-import Polysemy.Req (reqToIO)  -- | AUR response data AurReply a = AurReply@@ -106,7 +105,7 @@ data Aur m a where   SearchByName :: String -> Aur m (Maybe AurSearch)   InfoByName :: String -> Aur m (Maybe AurInfo)-  IsInAur :: PackageName -> Aur m Bool+  IsInAur :: HasMyName n => n -> Aur m Bool  -- searchByName @@ -119,12 +118,12 @@ infoByName :: Member Aur r => String -> Sem r (Maybe AurInfo)  -- | Check whether a __haskell__ package exists in AUR-isInAur :: Member Aur r => PackageName -> Sem r Bool+isInAur :: (HasMyName n, Member Aur r) => n -> Sem r Bool baseURL :: Url 'Https baseURL = https "aur.archlinux.org" /: "rpc"  -- | Run 'Aur' effect.-aurToIO :: Member (Embed IO) r => Sem (Aur ': r) a -> Sem r a+aurToIO :: Members [WithMyErr, Embed IO] r => Sem (Aur ': r) a -> Sem r a aurToIO = interpret $ \case   (SearchByName name) -> do     let parms =@@ -133,7 +132,7 @@             <> "by" =: ("name" :: Text)             <> "arg" =: (pack name)         r = req GET baseURL NoReqBody jsonResponse parms-    response <- reqToIO r+    response <- interceptHttpException $ runReq defaultHttpConfig r     let body :: AurReply AurSearch = responseBody response     return $ case r_resultcount body of       1 -> Just . head $ r_results body@@ -145,13 +144,13 @@             <> "by" =: ("name" :: Text)             <> "arg[]" =: (pack name)         r = req GET baseURL NoReqBody jsonResponse parms-    response <- reqToIO r+    response <- interceptHttpException $ runReq defaultHttpConfig r     let body :: AurReply AurInfo = responseBody response     return $ case r_resultcount body of       1 -> Just . head $ r_results body       _ -> Nothing   (IsInAur name) -> do-    result <- aurToIO . searchByName . fixName $ unPackageName name+    result <- aurToIO . searchByName . unCommunityName . toCommunityName $ name     return $ case result of       Just _ -> True       _ -> False
src/Distribution/ArchHs/Community.hs view
@@ -1,29 +1,31 @@ {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeApplications #-}  -- | Copyright: (c) 2020 berberman -- SPDX-License-Identifier: MIT -- Maintainer: berberman <1793913507@qq.com> -- Stability: experimental -- Portability: portable--- This module provides functios operating with @community.db@ of pacman.+-- This module provides functions operating with @community.db@ of pacman. module Distribution.ArchHs.Community   ( defaultCommunityPath,     loadProcessedCommunity,     isInCommunity,+    versionInCommunity,   ) where  import Conduit-import Control.Monad (when) import qualified Data.Conduit.Tar as Tar import qualified Data.Conduit.Zlib as Zlib-import Data.List (intercalate)-import Data.List.Split (splitOn)-import qualified Data.Set as Set+import qualified Data.Map.Strict as Map+import qualified Data.Text as T+import Distribution.ArchHs.Exception+import Distribution.ArchHs.Internal.Prelude+import Distribution.ArchHs.Name+import Distribution.ArchHs.PkgDesc import Distribution.ArchHs.Types-import Distribution.ArchHs.Utils (toLower')-import Distribution.Types.PackageName (PackageName, unPackageName)-import System.FilePath ((</>))  -- | Default path to @community.db@. defaultCommunityPath :: FilePath@@ -32,37 +34,46 @@ loadCommunity ::   (MonadResource m, PrimMonad m, MonadThrow m) =>   FilePath ->-  ConduitT i FilePath m ()+  ConduitT i (CommunityName, CommunityVersion) m () loadCommunity path = do   sourceFileBS path .| Zlib.ungzip .| Tar.untarChunks .| Tar.withEntries action   where     action header =-      when (Tar.headerFileType header == Tar.FTNormal) $-        yield $ Tar.headerFilePath header+      when (Tar.headerFileType header == Tar.FTNormal) $ do+        x <- mconcat <$> sinkList+        let txt = T.unpack . decodeUtf8 $ x+            result =+              let provided r = r Map.! "PROVIDES"+                  parseProvidedTerm t = let s = splitOn "=" t in (s ^. ix 0, s ^. ix 1)+               in case runDescFieldsParser (Tar.headerFilePath header) txt of+                    Right r -> case head $ r Map.! "NAME" of+                      "ghc" -> parseProvidedTerm <$> provided r+                      "ghc-libs" -> parseProvidedTerm <$> provided r+                      _ -> [(head $ r Map.! "NAME", extractVer . head $ r Map.! "VERSION")]+                    -- TODO: Drop it+                    Left _ -> []+            extractVer ver = head $+              splitOn "-" $ case splitOn ":" ver of+                (_ : v : []) -> v+                v : [] -> v+                _ -> fail "err" -cookCommunity :: (Monad m) => ConduitT FilePath FilePath m ()-cookCommunity = mapC (go . (splitOn "-"))-  where-    go list = case length list of-      3 -> list !! 0-      s ->-        if list !! 0 == "haskell"-          then intercalate "-" . fst . splitAt (s - 3) . tail $ list-          else intercalate "-" . fst . splitAt (s - 2) $ list+        yieldMany $ result & each . _1 %~ CommunityName --- | Load @community.db@ from @path@, removing @haskell-@ prefix.+-- | Load @community.db@ from @path@.+-- @desc@ files in the db will be parsed by @descParser@. loadProcessedCommunity :: (MonadUnliftIO m, PrimMonad m, MonadThrow m) => FilePath -> m CommunityDB-loadProcessedCommunity path = fmap Set.fromList $ runConduitRes $ loadCommunity path .| cookCommunity .| sinkList+loadProcessedCommunity path = Map.fromList <$> (runConduitRes $ loadCommunity path .| sinkList) --- | Check if a package from hackage exists in archlinux community repo.--- The following name conversion occurs during the checking to work with 'loadProcessedCommunity'.------ >>> "aeson" --> "aeson"--- >>> "Cabal" --> "cabal"--- >>> "haskell-a" --> "a"-isInCommunity :: Member CommunityEnv r => PackageName -> Sem r Bool-isInCommunity name =-  ask @CommunityDB >>= \db ->-    return $ case splitOn "-" . unPackageName $ name of-      ("haskell" : xs) -> intercalate "-" xs `elem` db-      _ -> (toLower' $ unPackageName name) `elem` db+-- | Check if a package exists in archlinux community repo.+-- See 'HasMyName'.+isInCommunity :: (HasMyName n, Member CommunityEnv r) => n -> Sem r Bool+isInCommunity name = ask @CommunityDB >>= \db -> return $ (toCommunityName name) `Map.member` db++-- | Get the version of a package in archlinux community repo.+-- If the package does not exist, 'PkgNotFound' will be thrown.+versionInCommunity :: (HasMyName n, Members [CommunityEnv, WithMyErr] r) => n -> Sem r CommunityVersion+versionInCommunity name =+  ask @CommunityDB >>= \db -> case db Map.!? (toCommunityName name) of+    Just x -> return x+    _ -> throw $ PkgNotFound name
src/Distribution/ArchHs/Core.hs view
@@ -1,4 +1,5 @@ {-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TypeApplications #-}  -- | Copyright: (c) 2020 berberman -- SPDX-License-Identifier: MIT@@ -14,27 +15,34 @@ where  import qualified Algebra.Graph.Labelled.AdjacencyMap as G-import Data.List (stripPrefix)+import Data.Char (toLower) import qualified Data.Map as Map import Data.Set (Set) import qualified Data.Set as Set-import Distribution.ArchHs.Hackage (getLatestCabal, getLatestSHA256)+import Distribution.ArchHs.Exception+import Distribution.ArchHs.Hackage+  ( getLatestCabal,+    getLatestSHA256,+  )+import Distribution.ArchHs.Internal.Prelude import Distribution.ArchHs.Local (ghcLibList, ignoreList)-import Distribution.ArchHs.PkgBuild (PkgBuild (..), mapLicense)+import Distribution.ArchHs.Name+import Distribution.ArchHs.PkgBuild+  ( PkgBuild (..),+    mapLicense,+  ) import Distribution.ArchHs.Types import Distribution.ArchHs.Utils import Distribution.Compiler (CompilerFlavor (..)) import Distribution.PackageDescription-import Distribution.Pretty (prettyShow) import Distribution.SPDX-import Distribution.System (Arch (X86_64), OS (Linux))+import Distribution.System+  ( Arch (X86_64),+    OS (Linux),+  ) import qualified Distribution.Types.BuildInfo.Lens as L import Distribution.Types.CondTree (simplifyCondTree) import Distribution.Types.Dependency (Dependency)-import Distribution.Types.PackageName (PackageName, unPackageName)-import Distribution.Types.UnqualComponentName (UnqualComponentName, unqualComponentNameToPackageName)-import Distribution.Types.Version (mkVersion)-import Distribution.Types.VersionRange (VersionRange, withinRange) import Distribution.Utils.ShortText (fromShortText)  archEnv :: FlagAssignment -> ConfVar -> Either ConfVar Bool@@ -232,7 +240,7 @@   cabal <- packageDescription <$> (getLatestCabal name)   _sha256sums <- (\s -> "'" <> s <> "'") <$> getLatestSHA256 name   let _hkgName = pkg ^. pkgName & unPackageName-      rawName = toLower' _hkgName+      rawName = toLower <$> _hkgName       _pkgName = maybe rawName id $ stripPrefix "haskell-" rawName       _pkgVer = prettyShow $ getPkgVersion cabal       _pkgDesc = fromShortText $ synopsis cabal@@ -274,7 +282,7 @@                        )                     && notIgnore x               )-      depsToString deps = deps <&> (wrap . fixName . unPackageName . _depName) & mconcat+      depsToString deps = deps <&> (wrap . unCommunityName . toCommunityName . _depName) & mconcat       _depends = depsToString depends       _makeDepends = (if uusi then " 'uusi'" else "") <> depsToString makeDepends       _url = getUrl cabal
+ src/Distribution/ArchHs/Exception.hs view
@@ -0,0 +1,65 @@+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeApplications #-}++-- | Copyright: (c) 2020 berberman+-- SPDX-License-Identifier: MIT+-- Maintainer: berberman <1793913507@qq.com>+-- Stability: experimental+-- Portability: portable+-- Exceptions used in this project.+module Distribution.ArchHs.Exception+  ( WithMyErr,+    MyException (..),+    printHandledIOException,+    printAppResult,+    interceptHttpException,+  )+where++import qualified Colourista as C+import qualified Control.Exception as CE+import qualified Data.Text as T+import Distribution.ArchHs.Internal.Prelude+import Distribution.ArchHs.Name+import Distribution.ArchHs.Types+import Network.HTTP.Req (HttpException (..))++-- | Error effect of 'MyException'+type WithMyErr = Error MyException++-- | Custom exception used in this project+data MyException+  = forall n. (HasMyName n) => PkgNotFound n+  | VersionNotFound PackageName Version+  | TargetExist PackageName DependencyProvider+  | CyclicExist [PackageName]+  | NetworkException HttpException++instance Show MyException where+  show (PkgNotFound name) = "Unable to find [" <> (unPackageName $ toHackageName name) <> "] (hackage name) / [" <> (unCommunityName $ toCommunityName name) <> "] (community name)"+  show (VersionNotFound name version) = "Unable to find [" <> (unPackageName $ toHackageName name) <> "] (hackage name) / [" <> (unCommunityName $ toCommunityName name) <> "] (community name)" <> " " <> prettyShow version+  show (TargetExist name provider) = "Target [" <> unPackageName name <> "] has been provided by " <> show provider+  show (CyclicExist c) = "Graph contains a cycle " <> (show $ fmap unPackageName c)+  show (NetworkException (JsonHttpException s)) = "Failed to parse response " <> s+  show (NetworkException (VanillaHttpException e)) = show e++-- | Catch 'CE.IOException' and print it.+printHandledIOException :: IO () -> IO ()+printHandledIOException = CE.handle @CE.IOException (\e -> C.errorMessage $ "IOException: " <> (T.pack . show $ e))++-- | Print the result of 'errorToIOFinal'.+printAppResult :: IO (Either MyException ()) -> IO ()+printAppResult io =+  io >>= \case+    Left x -> C.errorMessage $ "Runtime Exception: " <> (T.pack . show $ x)+    _ -> C.successMessage "Success!"++-- | Catch the 'HttpException' thrown in 'IO' monad, then re-throw it with 'NetworkException'.+interceptHttpException :: Members [WithMyErr, Embed IO] r => IO a -> Sem r a+interceptHttpException io = do+  x <- embed $ CE.try io+  case x of+    Left err -> throw $ NetworkException err+    Right x' -> return x'
src/Distribution/ArchHs/Hackage.hs view
@@ -1,3 +1,5 @@+{-# LANGUAGE TypeApplications #-}+ -- | Copyright: (c) 2020 berberman -- SPDX-License-Identifier: MIT -- Maintainer: berberman <1793913507@qq.com>@@ -17,20 +19,28 @@   ) where -import Control.Applicative (Alternative ((<|>))) import qualified Data.ByteString as BS import qualified Data.Map as Map import Data.Maybe (fromJust)+import Distribution.ArchHs.Exception+import Distribution.ArchHs.Internal.Prelude import Distribution.ArchHs.Types-import Distribution.ArchHs.Utils (getPkgName, getPkgVersion)-import Distribution.Hackage.DB (HackageDB, VersionData (VersionData, cabalFile), readTarball, tarballHashes)+import Distribution.ArchHs.Utils+  ( getPkgName,+    getPkgVersion,+  )+import Distribution.Hackage.DB+  ( HackageDB,+    VersionData (VersionData, cabalFile),+    readTarball,+    tarballHashes,+  ) import Distribution.PackageDescription.Parsec (parseGenericPackageDescriptionMaybe)-import Distribution.Types.Flag (Flag)-import Distribution.Types.GenericPackageDescription (GenericPackageDescription, genPackageFlags, packageDescription)-import Distribution.Types.PackageName (PackageName)-import Distribution.Version (Version, nullVersion)-import System.Directory (findFile, getHomeDirectory, listDirectory)-import System.FilePath ((</>))+import System.Directory+  ( findFile,+    getHomeDirectory,+    listDirectory,+  )  -- | Look up hackage tarball path from @~/.cabal@. -- Arbitrary hackage mirror is potential to be selected.@@ -72,14 +82,14 @@   case Map.lookup name db of     (Just m) -> case Map.lookupMax m of       Just (_, vdata) -> return $ f vdata-      Nothing -> throw $ VersionError name nullVersion+      Nothing -> throw $ VersionNotFound name nullVersion     Nothing -> throw $ PkgNotFound name  -- | Get the latest 'GenericPackageDescription'. getLatestCabal :: Members [HackageEnv, WithMyErr] r => PackageName -> Sem r GenericPackageDescription getLatestCabal = withLatestVersion cabalFile --- | Get the latest SHA256 sum of the tarball . +-- | Get the latest SHA256 sum of the tarball . getLatestSHA256 :: Members [HackageEnv, WithMyErr] r => PackageName -> Sem r String getLatestSHA256 = withLatestVersion (\vdata -> tarballHashes vdata Map.! "sha256") @@ -90,7 +100,7 @@   case Map.lookup name db of     (Just m) -> case Map.lookup version m of       Just vdata -> return $ vdata & cabalFile-      Nothing -> throw $ VersionError name version+      Nothing -> throw $ VersionNotFound name version     Nothing -> throw $ PkgNotFound name  -- | Get flags of a package.
+ src/Distribution/ArchHs/Internal/NamePresetLoader.hs view
@@ -0,0 +1,51 @@+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE TemplateHaskell #-}++module Distribution.ArchHs.Internal.NamePresetLoader (loadNamePreset) where++import Data.Aeson+import qualified Data.ByteString as BS+import Data.Map.Strict (Map, fromList, keys, toList)+import Data.Tuple (swap)+import GHC.Generics (Generic)+import Language.Haskell.TH+import System.Directory (getCurrentDirectory)+import System.FilePath ((</>))++data NamePreset = NamePreset+  { falseList :: [String],+    preset :: Map String String+  }+  deriving stock (Generic)++instance FromJSON NamePreset++loadNamePreset :: DecsQ+loadNamePreset = do+  txt <- runIO $ getCurrentDirectory >>= \dot -> BS.readFile $ dot </> "data" </> "NAME_PRESET.json"+  let NamePreset {..} = case decodeStrict txt of+        Just x -> x+        _ -> error "Failed to parse json"+  a <- genFunc "communityToHackageP" preset+  b <- genFunc "hackageToCommunityP" $ fromList . fmap swap . toList $ preset+  c <- genArray "falseListP" falseList+  d <- genArray "communityListP" $ keys preset+  return [a, b, c, d]++genFunc :: String -> Map String String -> DecQ+genFunc name src = do+  let temp = genClause <$> toList src+  funD (mkName name) $ temp <> [nothingClause]+  where+    genClause (from, to) =+      clause+        [litP $ stringL from]+        (normalB $ [|Just|] `appE` (litE . stringL $ to))+        []++    nothingClause = clause [wildP] (normalB [|Nothing|]) []++genArray :: String -> [String] -> DecQ+genArray name src = funD (mkName name) [clause [] (normalB [|src|]) []]
+ src/Distribution/ArchHs/Internal/Prelude.hs view
@@ -0,0 +1,80 @@+-- | Copyright: (c) 2020 berberman+-- SPDX-License-Identifier: MIT+-- Maintainer: berberman <1793913507@qq.com>+-- Stability: experimental+-- Portability: portable+-- A re-export list.+module Distribution.ArchHs.Internal.Prelude+  ( NFData,+    Generic,+    HasCallStack,+    when,+    (<|>),+    splitOn,+    stripPrefix,+    isPrefixOf,+    isInfixOf,+    groupBy,+    intercalate,+    nub,+    sort,+    sortBy,+    (\\),+    encodeUtf8,+    decodeUtf8,+    prettyShow,+    simpleParsec,+    (</>),+    module Lens.Micro,+    module Polysemy,+    module Polysemy.Error,+    module Polysemy.Reader,+    module Polysemy.State,+    module Polysemy.Trace,+    module Distribution.Types.PackageDescription,+    module Distribution.Types.GenericPackageDescription,+    module Distribution.Types.Version,+    module Distribution.Types.VersionRange,+    module Distribution.Types.PackageName,+    module Distribution.Types.UnqualComponentName,+    module Distribution.Types.Flag,+  )+where++import Control.Applicative ((<|>))+import Control.DeepSeq (NFData)+import Control.Monad (when)+import Data.List+  ( groupBy,+    intercalate,+    isInfixOf,+    isPrefixOf,+    nub,+    sort,+    sortBy,+    stripPrefix,+    (\\),+  )+import Data.List.Split (splitOn)+import Data.Text.Encoding+  ( decodeUtf8,+    encodeUtf8,+  )+import Distribution.Parsec (simpleParsec)+import Distribution.Pretty (prettyShow)+import Distribution.Types.Flag+import Distribution.Types.GenericPackageDescription+import Distribution.Types.PackageDescription+import Distribution.Types.PackageName+import Distribution.Types.UnqualComponentName+import Distribution.Types.Version+import Distribution.Types.VersionRange+import GHC.Generics (Generic)+import GHC.Stack (HasCallStack)+import Lens.Micro+import Polysemy+import Polysemy.Error+import Polysemy.Reader+import Polysemy.State+import Polysemy.Trace+import System.FilePath ((</>))
src/Distribution/ArchHs/Local.hs view
@@ -11,7 +11,7 @@ where  import Distribution.ArchHs.Types-import Distribution.Types.PackageName (mkPackageName)+import Distribution.Types.PackageName  -- | Packages should be dropped in dependency resolving. ignoreList :: PkgList
+ src/Distribution/ArchHs/Name.hs view
@@ -0,0 +1,171 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE FlexibleInstances #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell #-}++-- | Copyright: (c) 2020 berberman+-- SPDX-License-Identifier: MIT+-- Maintainer: berberman <1793913507@qq.com>+-- Stability: experimental+-- Portability: portable+--+-- Naming conversion between haskell package in hackage and archlinux community repo.+--+-- To distribute a haskell package to archlinux, the name of package should be changed according to the naming convention:+--+--   (1) for haskell libraries, their names must have @haskell-@ prefix+--+--   (2) for programs, it depends on circumstances+--+--   (3) names should always be in lower case+--+-- However, it's not enough to prefix the string with @haskell-@ and trasform to lower case; in some special situations, the hackage name+-- may have @haskell-@ prefix already, or the case is irregular, thus we have to a name preset, @NAME_PRESET.json@, manually.+-- Once a package distributed to archlinux, whose name conform to above-mentioned situation, the name preset should be upgraded correspondingly.+--+-- @NAME_PRESET.json@ will be loaded during the compilation, generating haskell code to be called in runtime.+--+-- Converting a community name to hackage name following these steps:+--+--   (1) Find if the name preset contains this rule+--   (2) If it contains, then use it; or remove the @haskell-@ prefix+--+-- Converting a hackage name to community name following these steps:+--+--   (1) Find if the name preset contains this rule+--   (2) If it contains, then use it; or add the @haskell-@ prefix+--+-- For details, see the type 'MyName' and type class 'HasMyName' with its instances.+module Distribution.ArchHs.Name+  ( MyName,+    unMyName,+    HasMyName (..),+    NameRep (..),+    mToCommunityName,+    mToHackageName,+    toCommunityName,+    toHackageName,+    isHaskellPackage,+  )+where++import Data.Char (toLower)+import Data.String+  ( IsString,+    fromString,+  )+import Distribution.ArchHs.Internal.NamePresetLoader+import Distribution.ArchHs.Internal.Prelude+import Distribution.ArchHs.Types++-- | The representation of a package name.+data NameRep+  = -- |  archlinx community style+    CommunityRep+  | -- | hackage style+    HackageRep++$(loadNamePreset)++-- | Convert a name from community representation to hackage representation, according to the name preset.+-- If the preset doesn't contain this mapping rule, the function will return 'Nothing'.+-- This function is generated from @NAME_PRESET.json@+communityToHackageP :: MyName 'CommunityRep -> Maybe (MyName 'HackageRep)++-- | Convert a name from hackage representation to community representation, according to the name preset.+-- If the preset doesn't contain this mapping rule, the function will return 'Nothing'.+--+-- This function is generated from @NAME_PRESET.json@+hackageToCommunityP :: MyName 'HackageRep -> Maybe (MyName 'CommunityRep)++-- | Special haskell packages in community reop, which should be ignored in the process.+--+-- This function is generated from @NAME_PRESET.json@+falseListP :: [MyName 'CommunityRep]++-- | Community haskell packages of in the name preset.+--+-- This function is generated from @NAME_PRESET.json@+communityListP :: [MyName 'CommunityRep]++-- | A general package name representation.+-- It has a phantom @a@, which indexes this name.+-- Normally, the index should be the data kinds of 'NameRep'.+--+-- In Cabal API, packages' names are represented by the type 'PackageName';+-- in arch-hs, names parsed from @community.db@ are represented by the type 'CommunityName'.+-- It would be tedious to use two converting functions everywhere, so here comes a intermediate data type+-- to unify them, with type level constraints as bonus.+newtype MyName a = MyName+  { -- | Unwrap the value.+    unMyName :: String+  }+  deriving stock (Show, Read, Eq, Ord, Generic)+  deriving anyclass (NFData)++instance IsString (MyName a) where+  fromString = MyName++-- | 'HasMyName' indicates that the type @a@ can be converted to 'MyName'.+-- This is where the actually conversion occurs.+class HasMyName a where+  -- | To 'MyName' in hackage style.+  toHackageRep :: a -> MyName 'HackageRep++  -- | To 'MyName' in community style.+  toCommunityRep :: a -> MyName 'CommunityRep++instance HasMyName (MyName 'CommunityRep) where+  toHackageRep = toHackageRep . CommunityName . unMyName+  toCommunityRep = id++instance HasMyName (MyName 'HackageRep) where+  toHackageRep = id+  toCommunityRep = toCommunityRep . mkPackageName . unMyName++instance HasMyName PackageName where+  toHackageRep = MyName . unPackageName+  toCommunityRep = go . unPackageName+    where+      go s = case hackageToCommunityP (MyName s) of+        Just x -> x+        _ ->+          MyName . fmap toLower $+            ( if "haskell-" `isPrefixOf` s+                then s+                else "haskell-" <> s+            )++instance HasMyName CommunityName where+  toHackageRep = go . unCommunityName+    where+      go s = case communityToHackageP (MyName s) of+        Just x -> x+        _ -> MyName $ drop 8 s+  toCommunityRep = MyName . unCommunityName++-- | Back to 'CommunityName'.+mToCommunityName :: MyName 'CommunityRep -> CommunityName+mToCommunityName = CommunityName . unMyName++-- | Back to 'PackageName'.+mToHackageName :: MyName 'HackageRep -> PackageName+mToHackageName = mkPackageName . unMyName++-- | Convert @n@ to 'CommunityName'.+toCommunityName :: HasMyName n => n -> CommunityName+toCommunityName = mToCommunityName . toCommunityRep++-- | Convert @n@ to 'PackageName'.+toHackageName :: HasMyName n => n -> PackageName+toHackageName = mToHackageName . toHackageRep++-- | Judge if a package in archlinux community repo is haskell package.+--+-- i.e. it is in @preset@ or have @haskell-@ prefix, and is not present in @falseList@ of @NAME_PRESET.json@.+isHaskellPackage :: CommunityName -> Bool+isHaskellPackage name =+  let rep = toCommunityRep name+   in (rep `elem` communityListP || "haskell-" `isPrefixOf` (unMyName rep)) && rep `notElem` falseListP
+ src/Distribution/ArchHs/OptionReader.hs view
@@ -0,0 +1,115 @@+{-# LANGUAGE OverloadedStrings #-}++-- | Copyright: (c) 2020 berberman+-- SPDX-License-Identifier: MIT+-- Maintainer: berberman <1793913507@qq.com>+-- Stability: experimental+-- Portability: portable+-- This module defines input patterns used in executables' cli.+-- "Options.Applicative" is re-exported.+module Distribution.ArchHs.OptionReader+  ( optFlagReader,+    optSkippedReader,+    optExtraCabalReader,+    optVersionReader,+    optPackageNameReader,+    module Options.Applicative,+  )+where++import qualified Data.Map.Strict as Map+import Data.Void (Void)+import Distribution.ArchHs.Internal.Prelude+import Distribution.ArchHs.Utils+import Options.Applicative+import System.FilePath (takeExtension)+import qualified Text.Megaparsec as M+import qualified Text.Megaparsec.Char as M++readFlag :: [(String, String, Bool)] -> Map.Map PackageName FlagAssignment+readFlag [] = Map.empty+readFlag list =+  Map.fromList+    . fmap (\l -> (mkPackageName . (^. _1) . head $ l, foldr (\(_, f, v) acc -> insertFlagAssignment (mkFlagName f) v acc) (mkFlagAssignment []) l))+    . groupBy (\a b -> uncurry (==) (getTwo _1 a b))+    $ list++-- | Read a set of package name with flag assignments.+--+-- >>> f ""+-- Right (fromList [])+-- >>> f "package_name:flag_name:true"+-- Right (fromList [(PackageName "package_name",fromList [(FlagName "flag_name",(1,True))])])+-- >>> f "package_name:flag_name_1:true,package_name:flag_name_2:false"+-- Right (fromList [(PackageName "package_name",fromList [(FlagName "flag_name_1",(1,True)),(FlagName "flag_name_2",(1,False))])])+-- >>> f "package_name_1:flag_name_1:false,package_name_2:flag_name_2:true"+-- Right (fromList [(PackageName "package_name_1",fromList [(FlagName "flag_name_1",(1,False))]),(PackageName "package_name_2",fromList [(FlagName "flag_name_2",(1,True))])])+-- >>> f "zzz"+-- Left "1:4:\n  |\n1 | zzz\n  |    ^\nunexpected end of input\nexpecting ':'\n"+optFlagReader :: ReadM (Map.Map PackageName FlagAssignment)+optFlagReader =+  eitherReader+    ( \s -> case M.parse optFlagParser "" s of+        Right x -> Right x+        Left err -> Left $ M.errorBundlePretty err+    )++optFlagParser :: M.Parsec Void String (Map.Map PackageName FlagAssignment)+optFlagParser =+  readFlag+    <$> ( do+            pkg <- M.manyTill M.anySingle $ M.single ':'+            flg <- M.manyTill M.anySingle $ M.single ':'+            b <- bool+            return (pkg, flg, b)+        )+    `M.sepBy` ","+  where+    bool = do+      s <- M.string "true" <|> M.string "false"+      case s of+        "true" -> return True+        "false" -> return False+        _ -> fail $ "unknown bool: " <> s++-- | Read skipped components.+-- This never fails, i.e. the return value will be 'Right'.+-- >>> f ""+-- Right [""]+-- >>> f "component_1,component_2"+-- Right ["component_1","component_2"]+optSkippedReader :: ReadM [String]+optSkippedReader = eitherReader $ Right . splitOn ","++-- | Read extra cabal files.+--+-- >>> f ""+-- Left "Unexpected file name: "+-- >>> f "a.cabal"+-- Right ["a.cabal"]+-- >>> f "a.cabal,b.cabal"+-- Right ["a.cabal","b.cabal"]+-- >>> f "a.what,b.cabal"+-- Left "Unexpected file name: a.what"+optExtraCabalReader :: ReadM [FilePath]+optExtraCabalReader = eitherReader $ \x ->+  let split = splitOn "," x+      check = map (\e -> if takeExtension e == ".cabal" then (e, True) else (e, False)) split+      failed = map fst . filter (not . snd) $ check+      successful = map fst . filter snd $ check+   in if failed /= [] then Left ("Unexpected file name: " <> intercalate ", " failed) else Right $ successful++-- | Read a 'Version'+-- This function calls 'simpleParsec'.+optVersionReader :: ReadM Version+optVersionReader =+  eitherReader+    ( \s -> case simpleParsec s of+        Just v -> Right v+        _ -> Left $ "Failed to parse version: " <> s+    )++-- | Read a 'PackageName'+-- This function never fails, because it just wraps the input string with 'mkPackageName'.+optPackageNameReader :: ReadM PackageName+optPackageNameReader = eitherReader $ Right . mkPackageName
src/Distribution/ArchHs/PP.hs view
@@ -1,7 +1,12 @@ {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-}-{-# OPTIONS_HADDOCK hide #-} +-- | Copyright: (c) 2020 berberman+-- SPDX-License-Identifier: MIT+-- Maintainer: berberman <1793913507@qq.com>+-- Stability: experimental+-- Portability: portable+-- This module provides simple pretty-printing functions work with the cli program. module Distribution.ArchHs.PP   ( prettySkip,     prettyFlagAssignments,@@ -12,20 +17,10 @@ where  import qualified Colourista as C-import Data.List (intercalate) import qualified Data.Map.Strict as Map import qualified Data.Text as T+import Distribution.ArchHs.Internal.Prelude import Distribution.ArchHs.Types-import Distribution.PackageDescription-  ( Flag (..),-    FlagAssignment,-    unFlagAssignment,-    unFlagName,-  )-import Distribution.Types.PackageName-  ( PackageName,-    unPackageName,-  )  prettySkip :: [String] -> String prettySkip = C.formatWith [C.magenta] . intercalate ", "@@ -39,7 +34,7 @@ prettyDeps :: [PackageName] -> String prettyDeps =   mconcat-    . fmap (\(i, n) -> show @Int i <> ". " <> unPackageName n <> "\n")+    . fmap (\(i, n) -> show (i :: Int) <> ". " <> unPackageName n <> "\n")     . zip [1 ..]  prettyFlags :: [(PackageName, [Flag])] -> String
src/Distribution/ArchHs/PkgBuild.hs view
@@ -216,5 +216,5 @@     install -D -m744 register.sh "$$pkgdir"/usr/share/haskell/register/$$pkgname.sh     install -D -m744 unregister.sh "$$pkgdir"/usr/share/haskell/unregister/$$pkgname.sh     runhaskell Setup copy --destdir="$$pkgdir"$licenseF-  } +  } |]
src/Distribution/ArchHs/PkgDesc.hs view
@@ -1,3 +1,6 @@+{-# LANGUAGE DeriveAnyClass #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE DerivingStrategies #-} {-# LANGUAGE RecordWildCards #-}  -- | Copyright: (c) 2020 berberman@@ -11,12 +14,14 @@     DescParser,     descParser,     descFieldsParser,+    runDescFieldsParser,     runDescParser,   ) where  import qualified Data.Map.Strict as Map import Data.Void (Void)+import Distribution.ArchHs.Internal.Prelude import Text.Megaparsec import Text.Megaparsec.Char @@ -26,14 +31,20 @@ -- | Package description file of a installed system package, -- which lies in @repo.db@ file. data PkgDesc = PkgDesc-  { name :: String,-    version :: String,-    desc :: String,-    url :: String,-    license :: String,-    depends :: [String],-    makeDepends :: [String]+  { _name :: String,+    _version :: String,+    _desc :: String,+    _url :: Maybe String,+    _license :: Maybe String,+    _provides :: [String],+    _optDepends :: [String],+    _replaces :: [String],+    _conflicts :: [String],+    _depends :: [String],+    _makeDepends :: [String]   }+  deriving stock (Show, Eq, Generic)+  deriving anyclass (NFData)  -- Common fields {- fieldList =@@ -63,11 +74,10 @@   Map.fromList     <$> ( do             sep-            field <- many (upperChar <|> digitChar)-            sep+            field <- manyTill anySingle sep             _ <- newline-            content <- manyTill line (lookAhead sep <|> () <$ eol <|> eof)-            return (field, content)+            content <- manyTill line (lookAhead sep <|> eof)+            return (field, filter (/= "") content)         )     `manyTill` eof   where@@ -79,13 +89,17 @@ descParser =   descFieldsParser     >>= ( \fields -> do-            name <- lookupSingle fields "NAME"-            version <- lookupSingle fields "VERSION"-            desc <- lookupSingle fields "DESC"-            url <- lookupSingle fields "URL"-            license <- lookupSingle fields "LICENSE"-            depends <- lookupList fields "DEPENDS"-            makeDepends <- lookupList fields "MAKEDEPENDS"+            _name <- lookupSingle fields "NAME"+            _version <- lookupSingle fields "VERSION"+            _desc <- lookupSingle fields "DESC"+            _url <- lookupSingleMaybe fields "URL"+            _license <- lookupSingleMaybe fields "LICENSE"+            _depends <- lookupList fields "DEPENDS"+            _makeDepends <- lookupList fields "MAKEDEPENDS"+            _provides <- lookupList fields "PROVIDES"+            _optDepends <- lookupList fields "OPTDEPENDS"+            _replaces <- lookupList fields "REPLACES"+            _conflicts <- lookupList fields "CONFLICTS"             return PkgDesc {..}         )   where@@ -94,10 +108,19 @@         (e : _) -> return e         _ -> fail $ "Expect a singleton " <> f       _ -> fail $ "Unable to find field " <> f+    lookupSingleMaybe fields f = return $ case Map.lookup f fields of+      (Just x) -> case x of+        (e : _) -> Just e+        _ -> Nothing+      _ -> Nothing     lookupList fields f = return $ case Map.lookup f fields of       (Just x) -> x       _ -> [] +-- | Run the desc fields parser.+runDescFieldsParser :: String -> String -> Either (ParseErrorBundle String Void) (Map.Map String [String])+runDescFieldsParser = parse descFieldsParser+ -- | Run the desc parser.-runDescParser :: String -> Either (ParseErrorBundle String Void) PkgDesc-runDescParser = parse descParser "Desc"+runDescParser :: String -> String -> Either (ParseErrorBundle String Void) PkgDesc+runDescParser = parse descParser
src/Distribution/ArchHs/Types.hs view
@@ -5,7 +5,6 @@ {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE TemplateHaskell #-}-{-# OPTIONS_HADDOCK ignore-exports #-}  -- | Copyright: (c) 2020 berberman -- SPDX-License-Identifier: MIT@@ -16,12 +15,12 @@ module Distribution.ArchHs.Types   ( PkgList,     ComponentPkgList,+    CommunityName (..),+    CommunityVersion,     CommunityDB,     HackageEnv,     CommunityEnv,     FlagAssignmentsEnv,-    WithMyErr,-    MyException (..),     DependencyType (..),     DependencyKind (..),     DependencyProvider (..),@@ -35,35 +34,13 @@     depName,     depType,     DependencyRecord,-    HasCallStack,-    module Polysemy,-    module Polysemy.Error,-    module Polysemy.Reader,-    module Polysemy.State,-    module Polysemy.Trace,-    module Lens.Micro,   ) where -import Control.DeepSeq (NFData) import Data.Map.Strict (Map)-import Data.Set (Set)+import Distribution.ArchHs.Internal.Prelude import Distribution.Hackage.DB (HackageDB)-import Distribution.PackageDescription (FlagAssignment)-import Distribution.Pretty (prettyShow)-import Distribution.Types.PackageName (PackageName, unPackageName)-import Distribution.Types.UnqualComponentName (UnqualComponentName, unUnqualComponentName)-import Distribution.Types.Version (Version)-import Distribution.Version (VersionRange)-import GHC.Generics (Generic)-import GHC.Stack (HasCallStack)-import Lens.Micro import Lens.Micro.TH (makeLenses)-import Polysemy-import Polysemy.Error-import Polysemy.Reader-import Polysemy.State-import Polysemy.Trace  -- | A list of 'PackageName' type PkgList = [PackageName]@@ -71,8 +48,19 @@ -- | A list of component represented by 'UnqualComponentName' and its dependencies collected in a 'PkgList' type ComponentPkgList = [(UnqualComponentName, PkgList)] +-- | Name of packages in archlinux community repo, a wrapper of 'String'.+newtype CommunityName = CommunityName+  { -- | Unwrap the value+    unCommunityName :: String+  }+  deriving stock (Show, Read, Eq, Ord, Generic)+  deriving anyclass (NFData)++-- | Version of packages in archlinux community repo+type CommunityVersion = String+ -- | Representation of @cummunity.db@-type CommunityDB = Set String+type CommunityDB = Map CommunityName CommunityVersion  -- | Reader effect of 'HackageDB' type HackageEnv = Reader HackageDB@@ -88,23 +76,6 @@  -- | Unused state effect type DependencyRecord = State (Map PackageName [VersionRange])---- | Error effect of 'MyException'-type WithMyErr = Error MyException---- | Custom exception used in this project-data MyException-  = PkgNotFound PackageName-  | VersionError PackageName Version-  | TargetExist PackageName DependencyProvider-  | CyclicError [PackageName]-  deriving stock (Eq)--instance Show MyException where-  show (PkgNotFound name) = "Unable to find [" <> unPackageName name <> "]"-  show (VersionError name version) = "Unable to find [" <> unPackageName name <> "-" <> prettyShow version <> "]"-  show (TargetExist name provider) = "Target [" <> unPackageName name <> "] has been provided by " <> show provider-  show (CyclicError c) = "Graph contains a cycle " <> (show $ fmap unPackageName c)  -- | The type of a dependency. Who requires this? data DependencyType
src/Distribution/ArchHs/Utils.hs view
@@ -8,12 +8,10 @@   ( getPkgName,     getPkgName',     getPkgVersion,-    toLower',     dependencyTypeToKind,     unExe,     unExeV,     unDepV,-    fixName,     getUrl,     getTwo,     buildDependsIfBuild,@@ -23,20 +21,10 @@   ) where -import Control.Applicative (Alternative ((<|>))) import Control.Monad ((<=<))-import Data.Char (toLower)-import Data.List.Split (splitOn)+import Distribution.ArchHs.Internal.Prelude import Distribution.ArchHs.Types-import Distribution.PackageDescription-  ( GenericPackageDescription,-    PackageDescription,-    homepage,-    package,-    packageDescription,-    repoLocation,-    sourceRepos,-  )+import Distribution.PackageDescription (repoLocation) import Distribution.Types.BuildInfo (BuildInfo (..)) import Distribution.Types.Dependency   ( Dependency,@@ -45,10 +33,11 @@   ) import Distribution.Types.ExeDependency (ExeDependency (..)) import qualified Distribution.Types.PackageId as I-import Distribution.Types.PackageName (PackageName, unPackageName) import Distribution.Utils.ShortText (fromShortText)-import Distribution.Version (Version, VersionRange)-import GHC.Stack (callStack, prettyCallStack)+import GHC.Stack+  ( callStack,+    prettyCallStack,+  )  -- | Extract the package name from a 'ExeDependency'. unExe :: ExeDependency -> PackageName@@ -83,27 +72,9 @@     f x = Just x     fromJust (Just x) = x     fromJust _ = fail "Impossible."-    safeHead [] = Nothing-    safeHead (x : _) = Just x     home = f . fromShortText . homepage $ cabal-    vcs = repoLocation <=< safeHead . sourceRepos $ cabal+    vcs = repoLocation <=< (^? ix 0) . sourceRepos $ cabal     fallback = Just $ "https://hackage.haskell.org/package/" <> (unPackageName $ getPkgName cabal)---- | Convert the hackage name into archlinux package name follow the convention.------ >>> fixName "haskell-A"--- "haskell-a"------ >>> fixName "QuickCheck"--- "haskell-quickcheck"-fixName :: String -> String-fixName s = case splitOn "-" s of-  ("haskell" : _) -> toLower' s-  _ -> "haskell-" <> toLower' s---- | Lower each 'Char's in 'String'.-toLower' :: String -> String-toLower' = fmap toLower  -- | Map 'DependencyType' with its data constructor tag 'DependencyKind'. dependencyTypeToKind :: DependencyType -> DependencyKind
− src/OptionParse.hs
@@ -1,85 +0,0 @@-{-# LANGUAGE OverloadedStrings #-}-{-# OPTIONS_HADDOCK hide #-}--module OptionParse-  ( readFlag,-    optFlagReader,-    optFlagParser,-    optSkippedReader,-    optExtraCabalReader,-    optVersionReader,-    optPackageNameReader,-    module Options.Applicative,-  )-where--import Data.List (groupBy, intercalate)-import Data.List.Split (splitOn)-import qualified Data.Map.Strict as Map-import Data.Void (Void)-import Distribution.ArchHs.Types-import Distribution.ArchHs.Utils-import Distribution.Parsec (simpleParsec)-import Distribution.Types.Flag (FlagAssignment, insertFlagAssignment, mkFlagAssignment, mkFlagName)-import Distribution.Types.PackageName (PackageName, mkPackageName)-import Distribution.Version (Version)-import Options.Applicative-import System.FilePath (takeExtension)-import qualified Text.Megaparsec as M-import qualified Text.Megaparsec.Char as M--readFlag :: [(String, String, Bool)] -> Map.Map PackageName FlagAssignment-readFlag [] = Map.empty-readFlag list =-  Map.fromList-    . fmap (\l -> (mkPackageName . (^. _1) . head $ l, foldr (\(_, f, v) acc -> insertFlagAssignment (mkFlagName f) v acc) (mkFlagAssignment []) l))-    . groupBy (\a b -> uncurry (==) (getTwo _1 a b))-    $ list--optFlagReader :: ReadM (Map.Map PackageName FlagAssignment)-optFlagReader =-  eitherReader-    ( \s -> case M.parse optFlagParser "" s of-        Right x -> Right x-        Left err -> Left $ M.errorBundlePretty err-    )--optFlagParser :: M.Parsec Void String (Map.Map PackageName FlagAssignment)-optFlagParser =-  readFlag-    <$> ( do-            pkg <- M.manyTill M.anySingle $ M.single ':'-            flg <- M.manyTill M.anySingle $ M.single ':'-            b <- bool-            return (pkg, flg, b)-        )-    `M.sepBy` ","-  where-    bool = do-      s <- M.string "true" <|> M.string "false"-      case s of-        "true" -> return True-        "false" -> return False-        _ -> fail $ "unknown bool: " <> s--optSkippedReader :: ReadM [String]-optSkippedReader = eitherReader $ Right . splitOn ","--optExtraCabalReader :: ReadM [FilePath]-optExtraCabalReader = eitherReader $ \x ->-  let splitted = splitOn "," x-      check = map (\e -> if takeExtension x == ".cabal" then (e, True) else (e, False)) splitted-      failed = map fst . filter (not . snd) $ check-      successful = map fst . filter snd $ check-   in if failed /= [] then Left ("Unexpected file name: " <> intercalate ", " failed) else Right successful--optVersionReader :: ReadM Version-optVersionReader =-  eitherReader-    ( \s -> case simpleParsec s of-        Just v -> Right v-        _ -> Left $ "Failed to parse version: " <> s-    )--optPackageNameReader :: ReadM PackageName-optPackageNameReader = eitherReader $ Right . mkPackageName
− src/Polysemy/Req.hs
@@ -1,12 +0,0 @@-{-# OPTIONS_HADDOCK hide #-}--module Polysemy.Req-  ( reqToIO,-  )-where--import Network.HTTP.Req-import Polysemy--reqToIO :: forall a r. Member (Embed IO) r => Req a -> Sem r a-reqToIO r = embed @IO $ runReq defaultHttpConfig r
+ submit/Main.hs view
@@ -0,0 +1,43 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RecordWildCards #-}++module Main (main) where++import qualified Colourista as C+import qualified Data.Text as T+import Distribution.ArchHs.Community+import Distribution.ArchHs.Exception+import Distribution.ArchHs.Internal.Prelude+import Distribution.ArchHs.Types+import Submit+import System.Directory (doesFileExist)+import System.Environment (lookupEnv)++main :: IO ()+main = printHandledIOException $+  do+    Options {..} <- runArgsParser+    let useDefaultCommunity = "/var/lib/pacman/sync/community.db" == optCommunityPath++    when useDefaultCommunity $ C.skipMessage "You didn't pass -c, use community db file from default path."++    community <- loadProcessedCommunity $ if useDefaultCommunity then defaultCommunityPath else optCommunityPath+    C.infoMessage "Loading community.db..."++    token <- lookupEnv "HACKAGE_API_TOKEN"++    when (token == Nothing) $ C.warningMessage "You didn't set HACKAGE_API_TOKEN, dry run only."++    let hasOutput = not $ null optOutput+    when (hasOutput) $ do+      C.infoMessage $ "Output will be dumped to " <> (T.pack optOutput) <> "."+      exist <- doesFileExist optOutput+      when exist $+        C.warningMessage $ "File " <> (T.pack optOutput) <> " already existed, overwrite it."+    C.infoMessage "Start running..."+    when (not $ optUpload || hasOutput) $+      C.warningMessage "Run diff only."+    runSubmit community (submit token optOutput optUpload) & printAppResult++runSubmit :: CommunityDB -> Sem '[CommunityEnv, WithMyErr, Embed IO, Final IO] a -> IO (Either MyException a)+runSubmit community = runFinal . embedToFinal . errorToIOFinal . (runReader community)
+ submit/Submit.hs view
@@ -0,0 +1,157 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TypeApplications #-}++module Submit+  ( Options (..),+    runArgsParser,+    submit,+  )+where++import qualified Colourista as C+import qualified Data.ByteString.Char8 as BS+import qualified Data.Map.Strict as Map+import Data.Maybe (fromJust)+import qualified Data.Text as T+import Data.Void (Void)+import Distribution.ArchHs.Exception+import Distribution.ArchHs.Internal.Prelude+import Distribution.ArchHs.Local+import Distribution.ArchHs.Name+import Distribution.ArchHs.Types+import Network.HTTP.Req+import Options.Applicative hiding (header)+import qualified Options.Applicative+import qualified Text.Megaparsec as M+import Text.Megaparsec.Char as M++data Options = Options+  { optCommunityPath :: FilePath,+    optOutput :: FilePath,+    optUpload :: Bool+  }++cmdOptions :: Parser Options+cmdOptions =+  Options+    <$> strOption+      ( long "community"+          <> metavar "PATH"+          <> short 'c'+          <> help "Path to community.db"+          <> showDefault+          <> value "/var/lib/pacman/sync/community.db"+      )+    <*> Options.Applicative.option+      str+      ( long "output"+          <> metavar "PATH"+          <> short 'o'+          <> help "Output path of generated .csv file"+          <> value ""+      )+    <*> switch+      ( long "upload"+          <> short 'u'+          <> help "Upload to hackage"+      )++runArgsParser :: IO Options+runArgsParser =+  execParser $+    info+      (cmdOptions <**> helper)+      ( fullDesc+          <> progDesc "Try to reach the TARGET QAQ."+          <> Options.Applicative.header "arch-hs-submit - a program submitting haskell packages in community to hackage arch distro."+      )++type DistroRecord = (String, String, String)++type DistroCSV = [DistroRecord]++renderDistroCSV :: DistroCSV -> String+renderDistroCSV = init . unlines . fmap (\(a, b, c) -> wrap a <> "," <> wrap b <> "," <> wrap c)+  where+    wrap x = "\"" <> x <> "\""++distroCSVParser :: M.Parsec Void String DistroCSV+distroCSVParser = M.sepBy distroRecordParser newline++distroRecordParser :: M.Parsec Void String DistroRecord+distroRecordParser =+  (M.between (M.char '"') (M.char '"') (M.many $ M.noneOf (",\"\n" :: String)) `M.sepBy` (M.char ',')) >>= \case+    (a : b : c : []) -> return (a, b, c)+    _ -> fail "Failed to parse record"++parseDistroCSV :: String -> DistroCSV+parseDistroCSV s = case M.parse distroCSVParser "DistroCSV" s of+  Left err -> fail $ M.errorBundlePretty err+  Right x -> x++genCSV :: Member CommunityEnv r => Sem r DistroCSV+genCSV = do+  db <- ask @CommunityDB+  let communityPackages = Map.toList db+      fields =+        communityPackages+          ^.. each+            . filtered (\(name, _) -> isHaskellPackage name)+            & mapped+            %~ _1+            <<%~ toHackageName+            & sortBy (\x y -> (x ^. _2 . _1) `compare` (y ^. _2 . _1))++      prefix = "https://www.archlinux.org/packages/community/x86_64/"+      processField (communityName, (hackageName, version)) =+        let communityName' =+              if (hackageName `elem` ghcLibList || hackageName == "ghc")+                then "ghc"+                else unCommunityName communityName+         in (unPackageName hackageName, version, prefix <> communityName')+  return $ processField <$> fields++submit :: Members [CommunityEnv, WithMyErr, Embed IO] r => Maybe String -> FilePath -> Bool -> Sem r ()+submit token output upload = do+  csv <- genCSV+  let v = renderDistroCSV csv+  embed $+    when (not . null $ output) $ do+      C.infoMessage $ "Write file: " <> T.pack output+      writeFile output v+  check csv+  interceptHttpException $+    when (token /= Nothing && upload) $ do+      C.infoMessage "Uploading..."+      let api = https "hackage.haskell.org" /: "distro" /: "Arch" /: "packages"+          r =+            req PUT api (ReqBodyBs . BS.pack $ v) bsResponse $+              header "Authorization" (BS.pack $ "X-ApiKey " <> fromJust token) <> header "Content-Type" "text/csv"+      result <- runReq defaultHttpConfig r+      C.infoMessage $ "StatusCode: " <> (T.pack . show $ responseStatusCode result)+      C.infoMessage $ "ResponseMessage: " <> (decodeUtf8 $ responseStatusMessage result)+      C.infoMessage "ResponseBody:"+      putStrLn . BS.unpack $ responseBody result++check :: Members [WithMyErr, Embed IO] r => DistroCSV -> Sem r ()+check community = do+  let api = https "hackage.haskell.org" /: "distro" /: "Arch" /: "packages.csv"+      r = req GET api NoReqBody bsResponse mempty+  embed $ C.infoMessage "Downloading csv from hackage..."+  result <- interceptHttpException $ runReq defaultHttpConfig r+  let bs = responseBody result+      hackage = parseDistroCSV . T.unpack $ decodeUtf8 bs++  let diffOld = hackage \\ community+      diffNew = community \\ hackage+      ppRecord b (name, version, url) = (if b then C.formatWith [C.green] else C.formatWith [C.red]) $ "(" <> name <> ", " <> version <> ", " <> url <> ")"++  embed $ case (diffNew <> diffOld) of+    [] -> return ()+    _ -> do+      putStrLn $ C.formatWith [C.magenta] "Diff:"+      putStr . unlines $ fmap (ppRecord False) diffOld+      putStrLn $ replicate 68 '-'+      putStr . unlines $ fmap (ppRecord True) diffNew++  embed . putStrLn $ "Found " <> show (length hackage) <> " packages with submitted distribution information in hackage, and " <> show (length community) <> " haskell packages in community."