packages feed

niv 0.2.1 → 0.2.2

raw patch · 10 files changed

+455/−271 lines, 10 filesdep +pureMD5PVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: pureMD5

API changes (from Hackage documentation)

- Niv.Cli: PackageName :: Text -> PackageName
- Niv.Cli: PackageSpec :: Object -> PackageSpec
- Niv.Cli: Sources :: HashMap PackageName PackageSpec -> Sources
- Niv.Cli: [unPackageName] :: PackageName -> Text
- Niv.Cli: [unPackageSpec] :: PackageSpec -> Object
- Niv.Cli: [unSources] :: Sources -> HashMap PackageName PackageSpec
- Niv.Cli: abortAttributeIsntAMap :: IO a
- Niv.Cli: abortSourcesDoesntExist :: IO a
- Niv.Cli: abortSourcesIsntJSON :: IO a
- Niv.Cli: attrsToSpec :: Attrs -> PackageSpec
- Niv.Cli: decodeFileStrict :: FromJSON a => FilePath -> IO (Maybe a)
- Niv.Cli: encodeFile :: ToJSON a => FilePath -> a -> IO ()
- Niv.Cli: forWithKeyM :: (Eq k, Hashable k, Monad m) => HashMap k v1 -> (k -> v1 -> m v2) -> m (HashMap k v2)
- Niv.Cli: forWithKeyM_ :: (Eq k, Hashable k, Monad m) => HashMap k v1 -> (k -> v1 -> m ()) -> m ()
- Niv.Cli: getSources :: IO Sources
- Niv.Cli: initNixSourcesJsonContent :: ByteString
- Niv.Cli: initNixSourcesNixContent :: ByteString
- Niv.Cli: instance Data.Aeson.Types.FromJSON.FromJSON Niv.Cli.PackageSpec
- Niv.Cli: instance Data.Aeson.Types.FromJSON.FromJSON Niv.Cli.Sources
- Niv.Cli: instance Data.Aeson.Types.FromJSON.FromJSONKey Niv.Cli.PackageName
- Niv.Cli: instance Data.Aeson.Types.ToJSON.ToJSON Niv.Cli.PackageSpec
- Niv.Cli: instance Data.Aeson.Types.ToJSON.ToJSON Niv.Cli.Sources
- Niv.Cli: instance Data.Aeson.Types.ToJSON.ToJSONKey Niv.Cli.PackageName
- Niv.Cli: instance Data.Hashable.Class.Hashable Niv.Cli.PackageName
- Niv.Cli: instance GHC.Base.Monoid Niv.Cli.PackageSpec
- Niv.Cli: instance GHC.Base.Semigroup Niv.Cli.PackageSpec
- Niv.Cli: instance GHC.Classes.Eq Niv.Cli.PackageName
- Niv.Cli: instance GHC.Show.Show Niv.Cli.PackageName
- Niv.Cli: instance GHC.Show.Show Niv.Cli.PackageSpec
- Niv.Cli: mapWithKeyM :: (Eq k, Hashable k, Monad m) => (k -> v1 -> m v2) -> HashMap k v1 -> m (HashMap k v2)
- Niv.Cli: mapWithKeyM_ :: (Eq k, Hashable k, Monad m) => (k -> v1 -> m ()) -> HashMap k v1 -> m ()
- Niv.Cli: newtype PackageName
- Niv.Cli: newtype PackageSpec
- Niv.Cli: newtype Sources
- Niv.Cli: pathNixSourcesJson :: FilePath
- Niv.Cli: pathNixSourcesNix :: FilePath
- Niv.Cli: setSources :: Sources -> IO ()
- Niv.Cli: warnIfOutdated :: IO ()
+ Data.Aeson.Extended: encodeFilePretty :: ToJSON a => FilePath -> a -> IO ()
+ Data.HashMap.Strict.Extended: forWithKeyM :: (Eq k, Hashable k, Monad m) => HashMap k v1 -> (k -> v1 -> m v2) -> m (HashMap k v2)
+ Data.HashMap.Strict.Extended: forWithKeyM_ :: (Eq k, Hashable k, Monad m) => HashMap k v1 -> (k -> v1 -> m ()) -> m ()
+ Data.HashMap.Strict.Extended: mapWithKeyM :: (Eq k, Hashable k, Monad m) => (k -> v1 -> m v2) -> HashMap k v1 -> m (HashMap k v2)
+ Data.HashMap.Strict.Extended: mapWithKeyM_ :: (Eq k, Hashable k, Monad m) => (k -> v1 -> m ()) -> HashMap k v1 -> m ()
+ Niv.Cli: githubUpdate' :: Update () ()
+ Niv.Cli: showPackage :: PackageName -> PackageSpec -> IO ()
+ Niv.Logger: blue :: String -> String
+ Niv.Logger: tblue :: Text -> Text
+ Niv.Logger: tyellow :: Text -> Text
+ Niv.Logger: yellow :: String -> String
+ Niv.Sources: PackageName :: Text -> PackageName
+ Niv.Sources: PackageSpec :: Object -> PackageSpec
+ Niv.Sources: SourceIsntJSON :: SourcesError
+ Niv.Sources: Sources :: HashMap PackageName PackageSpec -> Sources
+ Niv.Sources: SourcesDoesntExist :: SourcesError
+ Niv.Sources: SpecIsntAMap :: SourcesError
+ Niv.Sources: V1 :: SourcesNixVersion
+ Niv.Sources: V10 :: SourcesNixVersion
+ Niv.Sources: V2 :: SourcesNixVersion
+ Niv.Sources: V3 :: SourcesNixVersion
+ Niv.Sources: V4 :: SourcesNixVersion
+ Niv.Sources: V5 :: SourcesNixVersion
+ Niv.Sources: V6 :: SourcesNixVersion
+ Niv.Sources: V7 :: SourcesNixVersion
+ Niv.Sources: V8 :: SourcesNixVersion
+ Niv.Sources: V9 :: SourcesNixVersion
+ Niv.Sources: [unPackageName] :: PackageName -> Text
+ Niv.Sources: [unPackageSpec] :: PackageSpec -> Object
+ Niv.Sources: [unSources] :: Sources -> HashMap PackageName PackageSpec
+ Niv.Sources: abortSourcesDoesntExist :: IO a
+ Niv.Sources: abortSourcesIsntJSON :: IO a
+ Niv.Sources: abortSpecIsntAMap :: IO a
+ Niv.Sources: attrsToSpec :: Attrs -> PackageSpec
+ Niv.Sources: data SourcesError
+ Niv.Sources: data SourcesNixVersion
+ Niv.Sources: getSources :: IO Sources
+ Niv.Sources: getSourcesEither :: IO (Either SourcesError Sources)
+ Niv.Sources: initNixSourcesJsonContent :: ByteString
+ Niv.Sources: initNixSourcesNixContent :: ByteString
+ Niv.Sources: instance Data.Aeson.Types.FromJSON.FromJSON Niv.Sources.PackageSpec
+ Niv.Sources: instance Data.Aeson.Types.FromJSON.FromJSON Niv.Sources.Sources
+ Niv.Sources: instance Data.Aeson.Types.FromJSON.FromJSONKey Niv.Sources.PackageName
+ Niv.Sources: instance Data.Aeson.Types.ToJSON.ToJSON Niv.Sources.PackageSpec
+ Niv.Sources: instance Data.Aeson.Types.ToJSON.ToJSON Niv.Sources.Sources
+ Niv.Sources: instance Data.Aeson.Types.ToJSON.ToJSONKey Niv.Sources.PackageName
+ Niv.Sources: instance Data.Hashable.Class.Hashable Niv.Sources.PackageName
+ Niv.Sources: instance GHC.Base.Monoid Niv.Sources.PackageSpec
+ Niv.Sources: instance GHC.Base.Semigroup Niv.Sources.PackageSpec
+ Niv.Sources: instance GHC.Classes.Eq Niv.Sources.PackageName
+ Niv.Sources: instance GHC.Classes.Eq Niv.Sources.SourcesNixVersion
+ Niv.Sources: instance GHC.Enum.Bounded Niv.Sources.SourcesNixVersion
+ Niv.Sources: instance GHC.Enum.Enum Niv.Sources.SourcesNixVersion
+ Niv.Sources: instance GHC.Show.Show Niv.Sources.PackageName
+ Niv.Sources: instance GHC.Show.Show Niv.Sources.PackageSpec
+ Niv.Sources: latestVersionMD5 :: Text
+ Niv.Sources: md5ToSourcesVersion :: Text -> Maybe SourcesNixVersion
+ Niv.Sources: newtype PackageName
+ Niv.Sources: newtype PackageSpec
+ Niv.Sources: newtype Sources
+ Niv.Sources: pathNixSourcesJson :: FilePath
+ Niv.Sources: pathNixSourcesNix :: FilePath
+ Niv.Sources: setSources :: Sources -> IO ()
+ Niv.Sources: sourcesNixMD5 :: IO Text
+ Niv.Sources: sourcesVersionToMD5 :: SourcesNixVersion -> Text
+ Niv.Sources: sourcesVersionToText :: SourcesNixVersion -> Text
+ Niv.Sources: warnIfOutdated :: IO ()
+ Niv.Sources.Test: test_shippedSourcesNixIsLatest :: IO ()
- Niv.Cli: cmdAdd :: Maybe PackageName -> (PackageName, PackageSpec) -> IO ()
+ Niv.Cli: cmdAdd :: Update () a -> PackageName -> Attrs -> IO ()

Files

README.md view
@@ -199,7 +199,7 @@ ``` niv - dependency manager for Nix projects -version: 0.2.1+version: 0.2.2  Usage: niv COMMAND @@ -251,10 +251,10 @@  ``` Examples:--  niv update-  niv update nixpkgs-  niv update my-package -v beta-0.2+  +  niv update                     # update all packages+  niv update nixpkgs             # update nixpkgs+  niv update my-package -v beta-0.2 # update my-package to version "beta-0.2"  Usage: niv update [PACKAGE] ([-a|--attribute KEY=VAL] | [-b|--branch BRANCH] |                   [-o|--owner OWNER] | [-r|--repo REPO] | [-v|--version VERSION]
niv.cabal view
@@ -4,10 +4,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 7cabb5fdfc03b35748a6e8348284d303786488a08d6819852d4df7d428791e6e+-- hash: 4ae2ca2b71f11737dafa898e811c9e1a8f33a82bb9c5365bff2ff9e9d89dab93  name:           niv-version:        0.2.1+version:        0.2.2 synopsis:       Easy dependency management for Nix projects description:    Easy dependency management for Nix projects. category:       Development@@ -30,10 +30,14 @@  library   exposed-modules:+      Data.Aeson.Extended+      Data.HashMap.Strict.Extended       Niv.Cli       Niv.GitHub       Niv.GitHub.Test       Niv.Logger+      Niv.Sources+      Niv.Sources.Test       Niv.Test       Niv.Update       Niv.Update.Test@@ -57,6 +61,7 @@     , optparse-applicative     , process     , profunctors+    , pureMD5     , string-qq     , tasty     , tasty-hunit@@ -89,6 +94,7 @@     , optparse-applicative     , process     , profunctors+    , pureMD5     , string-qq     , text     , unliftio@@ -119,6 +125,7 @@     , optparse-applicative     , process     , profunctors+    , pureMD5     , string-qq     , tasty     , text
nix/sources.nix view
@@ -6,42 +6,40 @@   pkgs =     if hasNixpkgsPath     then-        if hasThisAsNixpkgsPath-        then import (builtins_fetchTarball { inherit (sources_nixpkgs) url sha256; }) {}-        else import <nixpkgs> {}+      if hasThisAsNixpkgsPath+      then import (builtins_fetchTarball { inherit (sources_nixpkgs) url sha256; }) {}+      else import <nixpkgs> {}     else-        import (builtins_fetchTarball { inherit (sources_nixpkgs) url sha256; }) {};+      import (builtins_fetchTarball { inherit (sources_nixpkgs) url sha256; }) {};    sources_nixpkgs =     if builtins.hasAttr "nixpkgs" sources     then sources.nixpkgs     else abort-    ''+      ''         Please specify either <nixpkgs> (through -I or NIX_PATH=nixpkgs=...) or         add a package called "nixpkgs" to your sources.json.-    '';+      '';    # fetchTarball version that is compatible between all the versions of Nix-  builtins_fetchTarball =-      { url, sha256 }@attrs:-      let-        inherit (builtins) lessThan nixVersion fetchTarball;-      in-        if lessThan nixVersion "1.12" then-          fetchTarball { inherit url; }-        else-          fetchTarball attrs;+  builtins_fetchTarball = { url, sha256 }@attrs:+    let+      inherit (builtins) lessThan nixVersion fetchTarball;+    in+      if lessThan nixVersion "1.12" then+        fetchTarball { inherit url; }+      else+        fetchTarball attrs;    # fetchurl version that is compatible between all the versions of Nix-  builtins_fetchurl =-      { url, sha256 }@attrs:-      let-        inherit (builtins) lessThan nixVersion fetchurl;-      in-        if lessThan nixVersion "1.12" then-          fetchurl { inherit url; }-        else-          fetchurl attrs;+  builtins_fetchurl = { url, sha256 }@attrs:+    let+      inherit (builtins) lessThan nixVersion fetchurl;+    in+      if lessThan nixVersion "1.12" then+        fetchurl { inherit url; }+      else+        fetchurl attrs;    # A wrapper around pkgs.fetchzip that has inspectable arguments,   # annoyingly this means we have to specify them@@ -57,37 +55,42 @@    sources = builtins.fromJSON (builtins.readFile ./sources.json); -  mapAttrs = builtins.mapAttrs or-    (f: set: with builtins;-      listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set)));+  mapAttrs = builtins.mapAttrs or (+    f: set: with builtins;+    listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set))+  );    # borrowed from nixpkgs   functionArgs = f: f.__functionArgs or (builtins.functionArgs f);   callFunctionWith = autoArgs: f: args:-    let auto = builtins.intersectAttrs (functionArgs f) autoArgs;-    in f (auto // args);+    let+      auto = builtins.intersectAttrs (functionArgs f) autoArgs;+    in+      f (auto // args);    getFetcher = spec:-    let fetcherName =-      if builtins.hasAttr "type" spec-      then builtins.getAttr "type" spec-      else "builtin-tarball";-    in builtins.getAttr fetcherName {-      "tarball" = fetchzip;-      "builtin-tarball" = builtins_fetchTarball;-      "file" = fetchurl;-      "builtin-url" = builtins_fetchurl;-    };+    let+      fetcherName =+        if builtins.hasAttr "type" spec+        then builtins.getAttr "type" spec+        else "builtin-tarball";+    in+      builtins.getAttr fetcherName {+        "tarball" = fetchzip;+        "builtin-tarball" = builtins_fetchTarball;+        "file" = fetchurl;+        "builtin-url" = builtins_fetchurl;+      }; }; # NOTE: spec must _not_ have an "outPath" attribute-mapAttrs (_: spec:-  if builtins.hasAttr "outPath" spec-  then abort-    "The values in sources.json should not have an 'outPath' attribute"-  else-    if builtins.hasAttr "url" spec && builtins.hasAttr "sha256" spec-    then-      spec //-      { outPath = callFunctionWith spec (getFetcher spec) { }; }-    else spec-  ) sources+mapAttrs (+  _: spec:+    if builtins.hasAttr "outPath" spec+    then abort+      "The values in sources.json should not have an 'outPath' attribute"+    else+      if builtins.hasAttr "url" spec && builtins.hasAttr "sha256" spec+      then+        spec // { outPath = callFunctionWith spec (getFetcher spec) {}; }+      else spec+) sources
+ src/Data/Aeson/Extended.hs view
@@ -0,0 +1,17 @@+module Data.Aeson.Extended where++import Data.Aeson (ToJSON)+import qualified Data.Aeson.Encode.Pretty as AesonPretty+import qualified Data.ByteString.Lazy as BL++--- Aeson++-- | Efficiently prettify and serialize a JSON value as a lazy 'BL.ByteString'+-- and write it to a file.+encodeFilePretty :: (ToJSON a) => FilePath -> a -> IO ()+encodeFilePretty fp = BL.writeFile fp . AesonPretty.encodePretty' config+  where+    config = AesonPretty.defConfig {+      AesonPretty.confTrailingNewline = True,+      AesonPretty.confCompare = compare+    }
+ src/Data/HashMap/Strict/Extended.hs view
@@ -0,0 +1,40 @@++module Data.HashMap.Strict.Extended where++import Control.Monad+import Data.Hashable (Hashable)+import qualified Data.HashMap.Strict as HMS++--- HashMap++forWithKeyM+  :: (Eq k, Hashable k, Monad m)+  => HMS.HashMap k v1+  -> (k -> v1 -> m v2)+  -> m (HMS.HashMap k v2)+forWithKeyM = flip mapWithKeyM++forWithKeyM_+  :: (Eq k, Hashable k, Monad m)+  => HMS.HashMap k v1+  -> (k -> v1 -> m ())+  -> m ()+forWithKeyM_ = flip mapWithKeyM_++mapWithKeyM+  :: (Eq k, Hashable k, Monad m)+  => (k -> v1 -> m v2)+  -> HMS.HashMap k v1+  -> m (HMS.HashMap k v2)+mapWithKeyM f m = do+    fmap mconcat $ forM (HMS.toList m) $ \(k, v) ->+      HMS.singleton k <$> f k v++mapWithKeyM_+  :: (Eq k, Hashable k, Monad m)+  => (k -> v1 -> m ())+  -> HMS.HashMap k v1+  -> m ()+mapWithKeyM_ f m = do+    forM_ (HMS.toList m) $ \(k, v) ->+      HMS.singleton k <$> f k v
src/Niv/Cli.hs view
@@ -1,5 +1,4 @@ {-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-}@@ -11,28 +10,27 @@  import Control.Applicative import Control.Monad-import Data.Aeson (FromJSON, FromJSONKey, ToJSON, ToJSONKey, (.=))+import Data.Aeson ((.=)) import Data.Char (isSpace)-import Data.FileEmbed (embedFile)+import Data.Functor+import Data.HashMap.Strict.Extended import Data.Hashable (Hashable)-import Data.Maybe (fromMaybe) import Data.String.QQ (s)-import Niv.Logger+import Data.Version (showVersion) import Niv.GitHub+import Niv.Logger+import Niv.Sources import Niv.Update+import System.Environment (getArgs) import System.Exit (ExitCode(ExitSuccess))-import System.FilePath ((</>), takeDirectory)+import System.FilePath (takeDirectory) import System.Process (readProcessWithExitCode) import UnliftIO-import Data.Version (showVersion) import qualified Data.Aeson as Aeson-import qualified Data.Aeson.Encode.Pretty as AesonPretty import qualified Data.ByteString as B import qualified Data.ByteString.Char8 as B8-import qualified Data.ByteString.Lazy as L import qualified Data.HashMap.Strict as HMS import qualified Data.Text as T-import qualified Data.Text.IO as T import qualified Options.Applicative as Opts import qualified Options.Applicative.Help.Pretty as Opts import qualified System.Directory as Dir@@ -41,9 +39,14 @@ import Paths_niv (version)  cli :: IO ()-cli = join $ Opts.execParser opts+cli = join $+    execParserPure' Opts.defaultPrefs opts <$> getArgs+      >>= Opts.handleParseResult   where-    opts = Opts.info (parseCommand <**> Opts.helper) $ mconcat desc+    execParserPure' pprefs pinfo [] = Opts.Failure $+      Opts.parserFailure pprefs pinfo Opts.ShowHelpText mempty+    execParserPure' pprefs pinfo args = Opts.execParserPure pprefs pinfo args+    opts = Opts.info (parseCommand <**> Opts.helper ) $ mconcat desc     desc =       [ Opts.fullDesc       , Opts.headerDoc $ Just $@@ -61,46 +64,10 @@     Opts.command "modify"  parseCmdModify <>     Opts.command "drop"  parseCmdDrop ) -newtype Sources = Sources-  { unSources :: HMS.HashMap PackageName PackageSpec }-  deriving newtype (FromJSON, ToJSON)--getSources :: IO Sources-getSources = do-    exists <- Dir.doesFileExist pathNixSourcesJson-    unless exists abortSourcesDoesntExist--    warnIfOutdated-    -- TODO: if doesn't exist: run niv init-    say $ "Reading sources file"-    decodeFileStrict pathNixSourcesJson >>= \case-      Just (Aeson.Object obj) ->-        fmap (Sources . mconcat) $-          forM (HMS.toList obj) $ \(k, v) ->-            case v of-              Aeson.Object v' ->-                pure $ HMS.singleton (PackageName k) (PackageSpec v')-              _ -> abortAttributeIsntAMap-      Just _ -> abortSourcesIsntAMap-      Nothing -> abortSourcesIsntJSON--setSources :: Sources -> IO ()-setSources sources = encodeFile pathNixSourcesJson sources--newtype PackageName = PackageName { unPackageName :: T.Text }-  deriving newtype (Eq, Hashable, FromJSONKey, ToJSONKey, Show)- parsePackageName :: Opts.Parser PackageName parsePackageName = PackageName <$>     Opts.argument Opts.str (Opts.metavar "PACKAGE") -newtype PackageSpec = PackageSpec { unPackageSpec :: Aeson.Object }-  deriving newtype (FromJSON, ToJSON, Show, Semigroup, Monoid)---- | Simply discards the 'Freedom'-attrsToSpec :: Attrs -> PackageSpec-attrsToSpec = PackageSpec . fmap snd- parsePackageSpec :: Opts.Parser PackageSpec parsePackageSpec =     (PackageSpec . HMS.fromList . fmap fixupAttributes) <$>@@ -193,12 +160,20 @@               createFile path initNixSourcesJsonContent               -- Imports @niv@ and @nixpkgs@ (19.03)               say "Importing 'niv' ..."-              cmdAdd Nothing (PackageName "nmattia/niv", PackageSpec HMS.empty)+              cmdAdd githubUpdate' (PackageName "niv")+                (specToFreeAttrs $ PackageSpec $ HMS.fromList+                  [ "owner" .= ("nmattia" :: T.Text)+                  , "repo" .= ("niv" :: T.Text)+                  ]+                )               say "Importing 'nixpkgs' ..."-              cmdAdd-                (Just (PackageName "nixpkgs"))-                ( PackageName "NixOS/nixpkgs-channels"-                , PackageSpec (HMS.singleton "branch" "nixos-19.03"))+              cmdAdd githubUpdate' (PackageName "nixpkgs")+                (specToFreeAttrs $ PackageSpec $ HMS.fromList+                  [ "owner" .= ("NixOS" :: T.Text)+                  , "repo" .= ("nixpkgs-channels" :: T.Text)+                  , "branch" .= ("nixos-19.03" :: T.Text)+                  ]+                )           , \path _content -> dontCreateFile path)         ] $ \(path, onCreate, onUpdate) -> do             exists <- Dir.doesFileExist path@@ -219,16 +194,37 @@  parseCmdAdd :: Opts.ParserInfo (IO ()) parseCmdAdd =-    Opts.info ((cmdAdd <$> optName <*> parsePackage) <**> Opts.helper) $+    Opts.info+      ((uncurry (cmdAdd githubUpdate') <$> parseArgs) <**> Opts.helper) $       mconcat desc   where-    optName :: Opts.Parser (Maybe PackageName)-    optName = Opts.optional $ PackageName <$>  Opts.strOption+    parseArgs :: Opts.Parser (PackageName, Attrs)+    parseArgs = collapse <$> parseNameAndGHShortcut <*> parsePackageSpec+    parseNameAndGHShortcut = (,) <$> optName <*> parseGitHubShortcut+    -- collaspe a "name or shortcut" with package spec+    collapse nameAndSpec pspec = (pname, specToLockedAttrs $ pspec <> repoAndOwner)+      where+        (pname, repoAndOwner) = case nameAndSpec of+          (Just pname', (_, spec)) -> (pname', PackageSpec spec)+          (Nothing, (pname', spec)) -> (pname', PackageSpec spec)+    optName = Opts.optional $ PackageName <$> Opts.strOption       ( Opts.long "name" <>         Opts.short 'n' <>         Opts.metavar "NAME" <>         Opts.help "Set the package name to <NAME>"       )++    -- parse a github shortcut of the form "owner/repo"+    parseGitHubShortcut = Opts.strArgument (Opts.metavar "PACKAGE") <&>+          -- parses a string "owner/repo" into package name (repo) and spec (owner ++          -- repo)+          \(T.pack -> str) ->+            case T.span (/= '/') str of+              (owner@(T.null -> False)+                , T.uncons -> Just ('/', repo@(T.null -> False))) ->+                  ( PackageName repo+                  , HMS.fromList [ "owner" .= owner, "repo" .= repo ])+              _ -> (PackageName str, HMS.empty)     desc =       [ Opts.fullDesc       , Opts.progDesc "Add dependency"@@ -240,38 +236,22 @@           "  niv add my-package -v alpha-0.1 -t http://example.com/archive/<version>.zip"       ] -cmdAdd :: Maybe PackageName -> (PackageName, PackageSpec) -> IO ()-cmdAdd mPackageName (PackageName str, cliSpec) =-    job ("Adding package " <> T.unpack str)  $ do--      -- Figures out the owner and repo-      let (packageName, defaultSpec) = case T.span (/= '/') str of-            ( owner@(T.null -> False)-              , T.uncons -> Just ('/', repo@(T.null -> False))) -> do-              (PackageName repo, HMS.fromList [ "owner" .= owner, "repo" .= repo ])-            _ -> (PackageName str, HMS.empty)-+cmdAdd :: Update () a -> PackageName -> Attrs -> IO ()+cmdAdd updateFunc packageName attrs = do+    job ("Adding package " <> T.unpack (unPackageName packageName)) $ do       sources <- unSources <$> getSources -      let packageName' = fromMaybe packageName mPackageName--      when (HMS.member packageName' sources) $-        abortCannotAddPackageExists packageName'--      let defaultSpec' = PackageSpec $ defaultSpec--      let initialSpec = specToLockedAttrs cliSpec <> specToFreeAttrs defaultSpec'+      when (HMS.member packageName sources) $+        abortCannotAddPackageExists packageName -      eFinalSpec <- fmap attrsToSpec <$> tryEvalUpdate-        initialSpec-        (githubUpdate nixPrefetchURL githubLatestRev githubRepo)+      eFinalSpec <- fmap attrsToSpec <$> tryEvalUpdate attrs updateFunc        case eFinalSpec of-        Left e -> abortUpdateFailed [(packageName', e)]+        Left e -> abortUpdateFailed [(packageName, e)]         Right finalSpec -> do           say $ "Writing new sources file"           setSources $ Sources $-            HMS.insert packageName' finalSpec sources+            HMS.insert packageName finalSpec sources  ------------------------------------------------------------------------------- -- SHOW@@ -287,32 +267,26 @@ cmdShow :: Maybe PackageName -> IO () cmdShow = \case     Just packageName -> do-      tsay $ "Showing package " <> unPackageName packageName-       sources <- unSources <$> getSources        case HMS.lookup packageName sources of-        Just (PackageSpec spec) -> do-          forM_ (HMS.toList spec) $ \(attrName, attrValValue) -> do-            let attrValue = case attrValValue of-                  Aeson.String str -> str-                  _ -> "<barabajagal>"-            tsay $ "  " <> attrName <> ": " <> attrValue+        Just pspec -> showPackage packageName pspec         Nothing -> abortCannotShowNoSuchPackage packageName      Nothing -> do-      say $ "Showing sources file"-       sources <- unSources <$> getSources+      forWithKeyM_ sources $ showPackage -      forWithKeyM_ sources $ \key (PackageSpec spec) -> do-        tsay $ "Showing " <> tbold (unPackageName key)-        forM_ (HMS.toList spec) $ \(attrName, attrValValue) -> do-          let attrValue = case attrValValue of-                Aeson.String str -> str-                _ -> tfaint "<barabajagal>"-          tsay $ "  " <> attrName <> ": " <> attrValue+showPackage :: PackageName -> PackageSpec -> IO ()+showPackage (PackageName pname) (PackageSpec spec) = do+    tsay $ tbold pname+    forM_ (HMS.toList spec) $ \(attrName, attrValValue) -> do+      let attrValue = case attrValValue of+            Aeson.String str -> str+            _ -> tfaint "<barabajagal>"+      tsay $ "  " <> attrName <> ": " <> attrValue + ------------------------------------------------------------------------------- -- UPDATE -------------------------------------------------------------------------------@@ -326,12 +300,14 @@     desc =       [ Opts.fullDesc       , Opts.progDesc "Update dependencies"-      , Opts.headerDoc $ Just $+      , Opts.headerDoc $ Just $ Opts.nest 2 $           "Examples:" Opts.<$$>           "" Opts.<$$>-          "  niv update" Opts.<$$>-          "  niv update nixpkgs" Opts.<$$>-          "  niv update my-package -v beta-0.2"+          Opts.vcat+            [ Opts.fill 30 "niv update" Opts.<+> "# update all packages",+              Opts.fill 30 "niv update nixpkgs" Opts.<+> "# update nixpkgs",+              Opts.fill 30 "niv update my-package -v beta-0.2" Opts.<+> "# update my-package to version \"beta-0.2\""+            ]       ]  specToFreeAttrs :: PackageSpec -> Attrs@@ -340,7 +316,6 @@ specToLockedAttrs :: PackageSpec -> Attrs specToLockedAttrs = fmap (Locked,) . unPackageSpec --- TODO: sexy logging + concurrent updates cmdUpdate :: Maybe (PackageName, PackageSpec) -> IO () cmdUpdate = \case     Just (packageName, cliSpec) ->@@ -473,60 +448,6 @@ -- Aux ------------------------------------------------------------------------------- ---- Aeson---- | Efficiently deserialize a JSON value from a file.--- If this fails due to incomplete or invalid input, 'Nothing' is--- returned.------ The input file's content must consist solely of a JSON document,--- with no trailing data except for whitespace.------ This function parses immediately, but defers conversion.  See--- 'json' for details.-decodeFileStrict :: (FromJSON a) => FilePath -> IO (Maybe a)-decodeFileStrict = fmap Aeson.decodeStrict . B.readFile---- | Efficiently serialize a JSON value as a lazy 'L.ByteString' and write it to a file.-encodeFile :: (ToJSON a) => FilePath -> a -> IO ()-encodeFile fp = L.writeFile fp . AesonPretty.encodePretty' config-  where-    config =  AesonPretty.defConfig { AesonPretty.confTrailingNewline = True, AesonPretty.confCompare = compare }----- HashMap--forWithKeyM-  :: (Eq k, Hashable k, Monad m)-  => HMS.HashMap k v1-  -> (k -> v1 -> m v2)-  -> m (HMS.HashMap k v2)-forWithKeyM = flip mapWithKeyM--forWithKeyM_-  :: (Eq k, Hashable k, Monad m)-  => HMS.HashMap k v1-  -> (k -> v1 -> m ())-  -> m ()-forWithKeyM_ = flip mapWithKeyM_--mapWithKeyM-  :: (Eq k, Hashable k, Monad m)-  => (k -> v1 -> m v2)-  -> HMS.HashMap k v1-  -> m (HMS.HashMap k v2)-mapWithKeyM f m = do-    fmap mconcat $ forM (HMS.toList m) $ \(k, v) ->-      HMS.singleton k <$> f k v--mapWithKeyM_-  :: (Eq k, Hashable k, Monad m)-  => (k -> v1 -> m ())-  -> HMS.HashMap k v1-  -> m ()-mapWithKeyM_ f m = do-    forM_ (HMS.toList m) $ \(k, v) ->-      HMS.singleton k <$> f k v- nixPrefetchURL :: Bool -> T.Text -> IO T.Text nixPrefetchURL unpack (T.unpack -> url) = do     (exitCode, sout, serr) <- runNixPrefetch@@ -558,53 +479,14 @@           _ -> False         _ -> False -warnIfOutdated :: IO ()-warnIfOutdated = do-    tryAny (B.readFile pathNixSourcesNix) >>= \case-      Left e -> T.putStrLn $ T.unlines-        [ "Could not read " <> T.pack pathNixSourcesNix-        , "Error: " <> tshow e-        ]-      Right content ->-        if shouldUpdateNixSourcesNix content-        then-          T.putStrLn $ T.unlines-            [ "WARNING: " <> T.pack pathNixSourcesNix <> " is out of date."-            , "Please run"-            , "  niv init"-            , "or add the following line in the " <> T.pack pathNixSourcesNix <> "  file:"-            , "  # niv: no_update"-            ]-        else pure ()---- | @nix/sources.nix@-pathNixSourcesNix :: FilePath-pathNixSourcesNix = "nix" </> "sources.nix"---- | Glue code between nix and sources.json-initNixSourcesNixContent :: B.ByteString-initNixSourcesNixContent = $(embedFile "nix/sources.nix")---- | @nix/sources.json"-pathNixSourcesJson :: FilePath-pathNixSourcesJson = "nix" </> "sources.json"---- | Empty JSON map-initNixSourcesJsonContent :: B.ByteString-initNixSourcesJsonContent = "{}"+-- | The IO (real) github update+githubUpdate' :: Update () ()+githubUpdate' = githubUpdate nixPrefetchURL githubLatestRev githubRepo  ------------------------------------------------------------------------------- -- Abort ------------------------------------------------------------------------------- -abortSourcesDoesntExist :: IO a-abortSourcesDoesntExist = abort $ T.unlines [ line1, line2 ]-  where-    line1 = "Cannot use " <> T.pack pathNixSourcesJson-    line2 = [s|-The sources file does not exist! You may need to run 'niv init'.-|]- abortSourcesIsntAMap :: IO a abortSourcesIsntAMap = abort $ T.unlines [ line1, line2 ]   where@@ -614,22 +496,6 @@ specification, e.g.:   { ... } |]--abortAttributeIsntAMap :: IO a-abortAttributeIsntAMap = abort $ T.unlines [ line1, line2 ]-  where-    line1 = "Cannot use " <> T.pack pathNixSourcesJson-    line2 = [s|-The package specifications in the sources file should be JSON maps from-attribute name to attribute value, e.g.:-  { "nixpkgs": { "foo": "bar" } }-|]--abortSourcesIsntJSON :: IO a-abortSourcesIsntJSON = abort $ T.unlines [ line1, line2 ]-  where-    line1 = "Cannot use " <> T.pack pathNixSourcesJson-    line2 = "The sources file should be JSON."  abortCannotAddPackageExists :: PackageName -> IO a abortCannotAddPackageExists (PackageName n) = abort $ T.unlines
src/Niv/Logger.hs view
@@ -53,6 +53,24 @@     ANSI.setSGRCode [ANSI.SetColor ANSI.Foreground ANSI.Vivid ANSI.Green] <>     str <> ANSI.setSGRCode [ANSI.Reset] +yellow :: String -> String+yellow str =+    ANSI.setSGRCode [ANSI.SetConsoleIntensity ANSI.BoldIntensity] <>+    ANSI.setSGRCode [ANSI.SetColor ANSI.Foreground ANSI.Vivid ANSI.Yellow] <>+    str <> ANSI.setSGRCode [ANSI.Reset]++tyellow :: T.Text -> T.Text+tyellow = dimap T.unpack T.pack yellow++blue :: String -> String+blue str =+    ANSI.setSGRCode [ANSI.SetConsoleIntensity ANSI.BoldIntensity] <>+    ANSI.setSGRCode [ANSI.SetColor ANSI.Foreground ANSI.Vivid ANSI.Blue] <>+    str <> ANSI.setSGRCode [ANSI.Reset]++tblue :: T.Text -> T.Text+tblue = dimap T.unpack T.pack blue+ red :: String -> String red str =     ANSI.setSGRCode [ANSI.SetColor ANSI.Foreground ANSI.Vivid ANSI.Red] <>
+ src/Niv/Sources.hs view
@@ -0,0 +1,216 @@+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE LambdaCase #-}++module Niv.Sources where++import Data.Aeson (FromJSON, FromJSONKey, ToJSON, ToJSONKey)+import Data.FileEmbed (embedFile)+import Data.Bifunctor (first)+import Data.Hashable (Hashable)+import Data.List+import Data.String.QQ (s)+import Niv.GitHub+import Niv.Logger+import Niv.Update+import System.FilePath ((</>))+import UnliftIO+import qualified Data.Aeson as Aeson+import qualified Data.Aeson.Extended as Aeson+import qualified Data.ByteString as B+import qualified Data.ByteString.Lazy.Char8 as BL8+import qualified Data.Digest.Pure.MD5 as MD5+import qualified Data.HashMap.Strict as HMS+import qualified Data.Text as T+import qualified System.Directory as Dir++-------------------------------------------------------------------------------+-- sources.json related+-------------------------------------------------------------------------------++data SourcesError+  = SourcesDoesntExist+  | SourceIsntJSON+  | SpecIsntAMap++newtype Sources = Sources+  { unSources :: HMS.HashMap PackageName PackageSpec }+  deriving newtype (FromJSON, ToJSON)++getSourcesEither :: IO (Either SourcesError Sources)+getSourcesEither = do+    Dir.doesFileExist pathNixSourcesJson >>= \case+      False -> pure $ Left SourcesDoesntExist+      True ->+        Aeson.decodeFileStrict pathNixSourcesJson >>= \case+          Just value -> case valueToSources value of+            Nothing -> pure $ Left SpecIsntAMap+            Just srcs -> pure $ Right srcs+          Nothing -> pure $ Left SourceIsntJSON+  where+    valueToSources :: Aeson.Value -> Maybe Sources+    valueToSources = \case+        Aeson.Object obj -> fmap (Sources . mapKeys PackageName) $ traverse+          (\case+            Aeson.Object obj' -> Just (PackageSpec obj')+            _ -> Nothing+          ) obj+        _ -> Nothing+    mapKeys :: (Eq k2, Hashable k2) => (k1 -> k2) -> HMS.HashMap k1 v -> HMS.HashMap k2 v+    mapKeys f = HMS.fromList . map (first f) . HMS.toList++getSources :: IO Sources+getSources = do+    warnIfOutdated+    getSourcesEither >>= either+      (\case+        SourcesDoesntExist -> abortSourcesDoesntExist+        SourceIsntJSON -> abortSourcesIsntJSON+        SpecIsntAMap -> abortSpecIsntAMap+      ) pure++setSources :: Sources -> IO ()+setSources sources = Aeson.encodeFilePretty pathNixSourcesJson sources++newtype PackageName = PackageName { unPackageName :: T.Text }+  deriving newtype (Eq, Hashable, FromJSONKey, ToJSONKey, Show)++newtype PackageSpec = PackageSpec { unPackageSpec :: Aeson.Object }+  deriving newtype (FromJSON, ToJSON, Show, Semigroup, Monoid)++-- | Simply discards the 'Freedom'+attrsToSpec :: Attrs -> PackageSpec+attrsToSpec = PackageSpec . fmap snd++-- | @nix/sources.json@+pathNixSourcesJson :: FilePath+pathNixSourcesJson = "nix" </> "sources.json"++--+-- ABORT messages+--++abortSourcesDoesntExist :: IO a+abortSourcesDoesntExist = abort $ T.unlines [ line1, line2 ]+  where+    line1 = "Cannot use " <> T.pack pathNixSourcesJson+    line2 = [s|+The sources file does not exist! You may need to run 'niv init'.+|]++abortSourcesIsntJSON :: IO a+abortSourcesIsntJSON = abort $ T.unlines [ line1, line2 ]+  where+    line1 = "Cannot use " <> T.pack pathNixSourcesJson+    line2 = "The sources file should be JSON."++abortSpecIsntAMap :: IO a+abortSpecIsntAMap = abort $ T.unlines [ line1, line2 ]+  where+    line1 = "Cannot use " <> T.pack pathNixSourcesJson+    line2 = [s|+The package specifications in the sources file should be JSON maps from+attribute name to attribute value, e.g.:+  { "nixpkgs": { "foo": "bar" } }+|]++-------------------------------------------------------------------------------+-- sources.nix related+-------------------------------------------------------------------------------+++-- | All the released versions of nix/sources.nix+data SourcesNixVersion+  = V1+  | V2+  | V3+  | V4+  | V5+  | V6+  | V7+  | V8+  | V9+  | V10+  deriving stock (Bounded, Enum, Eq)++-- | A user friendly version+sourcesVersionToText :: SourcesNixVersion -> T.Text+sourcesVersionToText = \case+    V1 -> "1"+    V2 -> "2"+    V3 -> "3"+    V4 -> "4"+    V5 -> "5"+    V6 -> "6"+    V7 -> "7"+    V8 -> "8"+    V9 -> "9"+    V10 -> "10"++latestVersionMD5 :: T.Text+latestVersionMD5 = sourcesVersionToMD5 maxBound++-- | Find a version based on the md5 of the nix/sources.nix+md5ToSourcesVersion :: T.Text -> Maybe SourcesNixVersion+md5ToSourcesVersion md5 =+    find (\snv -> sourcesVersionToMD5 snv == md5) [minBound .. maxBound]++-- | The MD5 sum of a particular version+sourcesVersionToMD5 :: SourcesNixVersion -> T.Text+sourcesVersionToMD5 = \case+    V1 -> "a7d3532c70fea66ffa25d6bc7ee49ad5"+    V2 -> "24cc0719fa744420a04361e23a3598d0"+    V3 -> "e01ed051e2c416e0fc7355fc72aeee3d"+    V4 -> "f754fe0e661b61abdcd32cb4062f5014"+    V5 -> "c34523590ff7dec7bf0689f145df29d1"+    V6 -> "8143f1db1e209562faf80a998be4929a"+    V7 -> "00a02cae76d30bbef96f001cabeed96f"+    V8 -> "e8b860753dd7fa1fd7b805dd836eb607"+    V9 -> "87149616c1b3b1e5aa73178f91c20b53"+    V10 -> "d8625c0a03dd935e1c79f46407faa8d3"++-- | The MD5 sum of ./nix/sources.nix+sourcesNixMD5 :: IO T.Text+sourcesNixMD5 = T.pack . show . MD5.md5 <$> BL8.readFile pathNixSourcesNix++-- | @nix/sources.nix@+pathNixSourcesNix :: FilePath+pathNixSourcesNix = "nix" </> "sources.nix"++warnIfOutdated :: IO ()+warnIfOutdated = do+    tryAny (BL8.readFile pathNixSourcesNix) >>= \case+      Left e -> tsay $ T.unlines -- warn with tsay+        [ T.unwords [ tyellow "WARNING:",  "Could not read" , T.pack pathNixSourcesNix ]+        , T.unwords [ "  ", "(", tshow e, ")" ]+        ]+      Right content -> do+        case md5ToSourcesVersion (T.pack $ show $ MD5.md5 content) of+          -- This is a custom or newer version, we don't do anything+          Nothing -> pure ()+          Just v+            -- The file is the latest+            | v == maxBound -> pure ()+            -- The file is older than than latest+            | otherwise -> do+                tsay $ T.unlines+                  [ T.unwords+                        [ tbold $ tblue "INFO:"+                        , "new sources.nix available:"+                        , sourcesVersionToText v, "->", sourcesVersionToText maxBound+                        ]+                  , "  Please run 'niv init' or add the following line in the " <>+                    T.pack pathNixSourcesNix <> " file:"+                  , "  # niv: no_update"+                  ]++-- | Glue code between nix and sources.json+initNixSourcesNixContent :: B.ByteString+initNixSourcesNixContent = $(embedFile "nix/sources.nix")++-- | Empty JSON map+initNixSourcesJsonContent :: B.ByteString+initNixSourcesJsonContent = "{}"
+ src/Niv/Sources/Test.hs view
@@ -0,0 +1,13 @@+module Niv.Sources.Test where++import Niv.Sources+import Test.Tasty.HUnit ((@=?))+import qualified Data.ByteString.Lazy as BL+import qualified Data.Digest.Pure.MD5 as MD5+import qualified Data.Text as T++-- | Ensure that the sources.nix we ship is tracked as the latest version+test_shippedSourcesNixIsLatest :: IO ()+test_shippedSourcesNixIsLatest =+    latestVersionMD5 @=?+      (T.pack . show . MD5.md5 . BL.fromStrict $ initNixSourcesNixContent)
src/Niv/Test.hs view
@@ -1,5 +1,6 @@ module Niv.Test (tests, test) where +import Niv.Sources.Test import Niv.GitHub.Test import Niv.Update.Test import qualified Test.Tasty as Tasty@@ -26,5 +27,8 @@         , Tasty.testCase "updates once" test_githubUpdatesOnce         , Tasty.testCase "doesn't override rev" test_githubDoesntOverrideRev         , Tasty.testCase "falls back to URL" test_githubURLFallback+        ]+    , Tasty.testGroup "sources.nix"+        [ Tasty.testCase "has latest version" test_shippedSourcesNixIsLatest         ]     ]