1
1
mirror of https://github.com/nmattia/niv.git synced 2024-11-08 08:26:02 +03:00
niv/app/Niv.hs

632 lines
21 KiB
Haskell
Raw Normal View History

2019-01-23 23:55:26 +03:00
{-# LANGUAGE DerivingStrategies #-}
2019-04-07 20:56:58 +03:00
{-# LANGUAGE TemplateHaskell #-}
2019-01-23 23:55:26 +03:00
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
2019-01-28 23:25:09 +03:00
{-# LANGUAGE QuasiQuotes #-}
2019-01-24 23:58:22 +03:00
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE ViewPatterns #-}
2019-01-23 23:55:26 +03:00
2019-06-09 23:42:35 +03:00
module Niv where
2019-01-28 23:29:35 +03:00
import Control.Applicative
2019-01-18 01:00:48 +03:00
import Control.Monad
2019-06-09 23:42:35 +03:00
import Data.Aeson (FromJSON, FromJSONKey, ToJSON, ToJSONKey, (.=))
2019-05-13 16:43:57 +03:00
import Data.Char (isSpace)
2019-04-07 20:56:58 +03:00
import Data.FileEmbed (embedFile)
2019-01-23 23:55:26 +03:00
import Data.Hashable (Hashable)
2019-06-12 14:48:09 +03:00
import Data.Maybe (fromMaybe)
2019-01-28 23:29:35 +03:00
import Data.String.QQ (s)
2019-06-09 23:42:35 +03:00
import Niv.GitHub
import Niv.Test
import Niv.Update
2019-01-28 23:51:10 +03:00
import System.Exit (exitFailure)
2019-01-28 23:29:35 +03:00
import System.FilePath ((</>), takeDirectory)
2019-01-27 23:18:10 +03:00
import System.Process (readProcess)
2019-03-05 22:44:41 +03:00
import UnliftIO
2019-01-28 23:29:35 +03:00
import qualified Data.Aeson as Aeson
2019-02-07 16:15:56 +03:00
import qualified Data.Aeson.Encode.Pretty as AesonPretty
2019-01-23 23:55:26 +03:00
import qualified Data.ByteString as B
2019-04-07 20:56:58 +03:00
import qualified Data.ByteString.Char8 as B8
2019-01-23 23:55:26 +03:00
import qualified Data.ByteString.Lazy as L
2019-05-14 15:36:31 +03:00
import qualified Data.HashMap.Strict as HMS
2019-01-23 23:55:26 +03:00
import qualified Data.Text as T
2019-05-13 16:43:57 +03:00
import qualified Data.Text.IO as T
2019-01-27 01:39:38 +03:00
import qualified GitHub as GH
2019-01-27 23:18:10 +03:00
import qualified Options.Applicative as Opts
2019-01-29 00:32:36 +03:00
import qualified Options.Applicative.Help.Pretty as Opts
2019-01-28 23:29:35 +03:00
import qualified System.Directory as Dir
2019-06-09 23:42:35 +03:00
import qualified Test.Tasty as Tasty
2019-01-18 01:00:48 +03:00
2019-01-29 00:32:36 +03:00
main :: IO ()
main = join $ Opts.execParser opts
where
opts = Opts.info (parseCommand <**> Opts.helper) $ mconcat desc
desc =
[ Opts.fullDesc
, Opts.header "NIV - Version manager for Nix projects"
]
parseCommand :: Opts.Parser (IO ())
parseCommand = Opts.subparser (
Opts.command "init" parseCmdInit <>
Opts.command "add" parseCmdAdd <>
Opts.command "show" parseCmdShow <>
Opts.command "update" parseCmdUpdate <>
Opts.command "drop" parseCmdDrop )
newtype Sources = Sources
2019-05-14 15:36:31 +03:00
{ unSources :: HMS.HashMap PackageName PackageSpec }
2019-01-24 23:58:22 +03:00
deriving newtype (FromJSON, ToJSON)
getSources :: IO Sources
getSources = do
exists <- Dir.doesFileExist pathNixSourcesJson
unless exists abortSourcesDoesntExist
2019-03-05 22:44:41 +03:00
warnIfOutdated
2019-01-30 20:42:14 +03:00
-- TODO: if doesn't exist: run niv init
putStrLn $ "Reading sources file"
decodeFileStrict pathNixSourcesJson >>= \case
2019-01-28 23:29:35 +03:00
Just (Aeson.Object obj) ->
fmap (Sources . mconcat) $
2019-05-14 15:36:31 +03:00
forM (HMS.toList obj) $ \(k, v) ->
2019-01-23 23:55:26 +03:00
case v of
2019-01-28 23:29:35 +03:00
Aeson.Object v' ->
2019-05-14 15:36:31 +03:00
pure $ HMS.singleton (PackageName k) (PackageSpec v')
2019-01-28 23:51:10 +03:00
_ -> abortAttributeIsntAMap
Just _ -> abortSourcesIsntAMap
Nothing -> abortSourcesIsntJSON
2019-01-23 23:55:26 +03:00
setSources :: Sources -> IO ()
setSources sources = encodeFile pathNixSourcesJson sources
2019-01-27 23:18:10 +03:00
2019-05-13 16:43:57 +03:00
newtype PackageName = PackageName { unPackageName :: T.Text }
2019-01-24 23:58:22 +03:00
deriving newtype (Eq, Hashable, FromJSONKey, ToJSONKey, Show)
2019-01-27 23:18:10 +03:00
parsePackageName :: Opts.Parser PackageName
parsePackageName = PackageName <$>
Opts.argument Opts.str (Opts.metavar "PACKAGE")
2019-01-23 23:55:26 +03:00
2019-06-09 23:42:35 +03:00
newtype PackageSpec = PackageSpec { unPackageSpec :: Aeson.Object }
2019-01-30 20:50:09 +03:00
deriving newtype (FromJSON, ToJSON, Show, Semigroup, Monoid)
2019-01-23 23:55:26 +03:00
2019-06-09 23:42:35 +03:00
-- | Simply discards the 'Freedom'
attrsToSpec :: Attrs -> PackageSpec
attrsToSpec = PackageSpec . fmap snd
2019-01-27 23:18:10 +03:00
parsePackageSpec :: Opts.Parser PackageSpec
2019-01-24 23:58:22 +03:00
parsePackageSpec =
2019-05-14 15:36:31 +03:00
(PackageSpec . HMS.fromList . fmap fixupAttributes) <$>
2019-01-24 23:58:22 +03:00
many parseAttribute
where
2019-05-13 16:43:57 +03:00
parseAttribute :: Opts.Parser (T.Text, T.Text)
2019-01-31 02:05:35 +03:00
parseAttribute =
2019-01-27 23:18:10 +03:00
Opts.option (Opts.maybeReader parseKeyVal)
( Opts.long "attribute" <>
Opts.short 'a' <>
2019-01-31 02:05:35 +03:00
Opts.metavar "KEY=VAL" <>
Opts.help "Set the package spec attribute <KEY> to <VAL>"
) <|> shortcutAttributes <|>
2019-01-29 00:32:36 +03:00
(("url_template",) <$> Opts.strOption
( Opts.long "template" <>
Opts.short 't' <>
Opts.metavar "URL" <>
2019-01-29 00:39:08 +03:00
Opts.help "Used during 'update' when building URL. Occurrences of <foo> are replaced with attribute 'foo'."
)) <|>
(("type",) <$> Opts.strOption
( Opts.long "type" <>
Opts.short 'T' <>
Opts.metavar "TYPE" <>
Opts.help "The type of the URL target. The value can be either 'file' or 'tarball'. If not set, the value is inferred from the suffix of the URL."
2019-01-29 00:32:36 +03:00
))
2019-01-23 23:55:26 +03:00
2019-01-24 23:58:22 +03:00
-- Parse "key=val" into ("key", "val")
2019-05-13 16:43:57 +03:00
parseKeyVal :: String -> Maybe (T.Text, T.Text)
2019-01-24 23:58:22 +03:00
parseKeyVal str = case span (/= '=') str of
2019-05-13 16:43:57 +03:00
(key, '=':val) -> Just (T.pack key, T.pack val)
2019-01-24 23:58:22 +03:00
_ -> Nothing
-- Shortcuts for common attributes
2019-05-13 16:43:57 +03:00
shortcutAttributes :: Opts.Parser (T.Text, T.Text)
2019-01-24 23:58:22 +03:00
shortcutAttributes = foldr (<|>) empty $ mkShortcutAttribute <$>
2019-01-29 00:32:36 +03:00
[ "branch", "owner", "repo", "version" ]
2019-01-24 23:58:22 +03:00
2019-06-09 23:42:35 +03:00
-- TODO: infer those shortcuts from 'Update' keys
2019-05-13 16:43:57 +03:00
mkShortcutAttribute :: T.Text -> Opts.Parser (T.Text, T.Text)
2019-01-27 23:18:10 +03:00
mkShortcutAttribute = \case
2019-05-13 16:43:57 +03:00
attr@(T.uncons -> Just (c,_)) -> (attr,) <$> Opts.strOption
( Opts.long (T.unpack attr) <>
2019-01-31 02:05:35 +03:00
Opts.short c <>
2019-05-13 16:43:57 +03:00
Opts.metavar (T.unpack $ T.toUpper attr) <>
2019-01-31 02:05:35 +03:00
Opts.help
2019-05-13 16:43:57 +03:00
( T.unpack $
2019-01-31 02:05:35 +03:00
"Equivalent to --attribute " <>
2019-05-13 16:43:57 +03:00
attr <> "=<" <> (T.toUpper attr) <> ">"
2019-01-31 02:05:35 +03:00
)
)
2019-01-28 23:51:10 +03:00
_ -> empty
2019-01-24 23:58:22 +03:00
2019-05-13 16:43:57 +03:00
fixupAttributes :: (T.Text, T.Text) -> (T.Text, Aeson.Value)
fixupAttributes (k, v) = (k, Aeson.String v)
2019-01-24 23:58:22 +03:00
2019-01-27 23:18:10 +03:00
parsePackage :: Opts.Parser (PackageName, PackageSpec)
2019-01-24 23:58:22 +03:00
parsePackage = (,) <$> parsePackageName <*> parsePackageSpec
2019-01-27 23:18:10 +03:00
-------------------------------------------------------------------------------
-- PACKAGE SPEC OPS
-------------------------------------------------------------------------------
2019-06-09 23:42:35 +03:00
test :: IO ()
test = Tasty.defaultMain $ Niv.Test.tests
2019-01-27 23:18:10 +03:00
2019-01-23 23:55:26 +03:00
-------------------------------------------------------------------------------
-- INIT
-------------------------------------------------------------------------------
2019-01-27 23:18:10 +03:00
parseCmdInit :: Opts.ParserInfo (IO ())
2019-01-29 00:32:36 +03:00
parseCmdInit = Opts.info (pure cmdInit <**> Opts.helper) $ mconcat desc
where
desc =
[ Opts.fullDesc
, Opts.progDesc
"Initialize a Nix project. Existing files won't be modified."
]
2019-01-23 23:55:26 +03:00
2019-01-18 01:00:48 +03:00
cmdInit :: IO ()
cmdInit = do
2019-01-28 23:25:09 +03:00
-- Writes all the default files
2019-03-05 22:44:41 +03:00
-- a path, a "create" function and an update function for each file.
2019-01-28 23:25:09 +03:00
forM_
2019-03-13 14:59:41 +03:00
[ ( pathNixSourcesNix
, (`createFile` initNixSourcesNixContent)
, \path content -> do
if shouldUpdateNixSourcesNix content
then do
putStrLn "Updating sources.nix"
2019-04-07 20:56:58 +03:00
B.writeFile path initNixSourcesNixContent
2019-03-13 14:59:41 +03:00
else putStrLn "Not updating sources.nix"
)
, ( pathNixSourcesJson
2019-03-05 22:44:41 +03:00
, \path -> do
createFile path initNixSourcesJsonContent
-- Imports @niv@ and @nixpkgs@ (18.09)
putStrLn "Importing 'niv' ..."
2019-05-14 15:36:31 +03:00
cmdAdd Nothing (PackageName "nmattia/niv", PackageSpec HMS.empty)
2019-03-05 22:44:41 +03:00
putStrLn "Importing 'nixpkgs' ..."
cmdAdd
(Just (PackageName "nixpkgs"))
( PackageName "NixOS/nixpkgs-channels"
2019-05-14 15:36:31 +03:00
, PackageSpec (HMS.singleton "branch" "nixos-18.09"))
2019-03-05 22:44:41 +03:00
, \path _content -> dontCreateFile path)
] $ \(path, onCreate, onUpdate) -> do
exists <- Dir.doesFileExist path
2019-04-07 20:56:58 +03:00
if exists then B.readFile path >>= onUpdate path else onCreate path
2019-03-05 22:44:41 +03:00
where
2019-04-07 20:56:58 +03:00
createFile :: FilePath -> B.ByteString -> IO ()
2019-03-05 22:44:41 +03:00
createFile path content = do
let dir = takeDirectory path
Dir.createDirectoryIfMissing True dir
putStrLn $ "Creating " <> path
2019-04-07 20:56:58 +03:00
B.writeFile path content
2019-03-05 22:44:41 +03:00
dontCreateFile :: FilePath -> IO ()
dontCreateFile path = putStrLn $ "Not creating " <> path
2019-01-18 01:00:48 +03:00
2019-01-23 23:55:26 +03:00
-------------------------------------------------------------------------------
-- ADD
-------------------------------------------------------------------------------
2019-01-27 23:18:10 +03:00
parseCmdAdd :: Opts.ParserInfo (IO ())
parseCmdAdd =
2019-01-31 02:05:35 +03:00
Opts.info ((cmdAdd <$> optName <*> parsePackage) <**> Opts.helper) $
2019-01-29 00:32:36 +03:00
mconcat desc
2019-01-27 23:18:10 +03:00
where
optName :: Opts.Parser (Maybe PackageName)
optName = Opts.optional $ PackageName <$> Opts.strOption
( Opts.long "name" <>
Opts.short 'n' <>
2019-01-31 02:05:35 +03:00
Opts.metavar "NAME" <>
Opts.help "Set the package name to <NAME>"
2019-01-27 23:18:10 +03:00
)
2019-01-29 00:32:36 +03:00
desc =
[ Opts.fullDesc
, Opts.progDesc "Add dependency"
, Opts.headerDoc $ Just $
"Examples:" Opts.<$$>
"" Opts.<$$>
" niv add stedolan/jq" Opts.<$$>
" niv add NixOS/nixpkgs-channels -n nixpkgs -b nixos-18.09" Opts.<$$>
2019-01-29 00:32:36 +03:00
" niv add my-package -v alpha-0.1 -t http://example.com/archive/<version>.zip"
]
2019-01-23 23:55:26 +03:00
2019-01-31 02:05:35 +03:00
cmdAdd :: Maybe PackageName -> (PackageName, PackageSpec) -> IO ()
2019-06-09 23:42:35 +03:00
cmdAdd mPackageName (PackageName str, cliSpec) = do
2019-01-27 01:39:38 +03:00
-- Figures out the owner and repo
2019-06-09 23:42:35 +03:00
let (packageName, defaultSpec) = case T.span (/= '/') str of
2019-05-13 16:43:57 +03:00
( owner@(T.null -> False)
, T.uncons -> Just ('/', repo@(T.null -> False))) -> do
2019-06-09 23:42:35 +03:00
(PackageName repo, HMS.fromList [ "owner" .= owner, "repo" .= repo ])
_ -> (PackageName str, HMS.empty)
2019-01-27 01:39:38 +03:00
sources <- unSources <$> getSources
2019-01-27 01:39:38 +03:00
2019-01-27 23:18:10 +03:00
let packageName' = fromMaybe packageName mPackageName
2019-01-27 01:39:38 +03:00
2019-05-14 15:36:31 +03:00
when (HMS.member packageName' sources) $
2019-01-28 23:51:10 +03:00
abortCannotAddPackageExists packageName'
2019-01-27 01:39:38 +03:00
2019-06-09 23:42:35 +03:00
let defaultSpec' = PackageSpec $ defaultSpec
spec'' <- attrsToSpec <$> evalUpdate
(specToLockedAttrs cliSpec <> specToFreeAttrs defaultSpec')
(githubUpdate nixPrefetchURL githubLatestRev githubRepo)
2019-01-27 01:39:38 +03:00
putStrLn $ "Writing new sources file"
setSources $ Sources $
2019-05-14 15:36:31 +03:00
HMS.insert packageName' spec'' sources
2019-01-24 23:58:22 +03:00
2019-01-23 23:55:26 +03:00
-------------------------------------------------------------------------------
-- SHOW
-------------------------------------------------------------------------------
2019-01-18 01:00:48 +03:00
2019-01-27 23:18:10 +03:00
parseCmdShow :: Opts.ParserInfo (IO ())
parseCmdShow = Opts.info (pure cmdShow <**> Opts.helper) Opts.fullDesc
2019-01-24 23:58:22 +03:00
2019-06-09 23:42:35 +03:00
-- TODO: nicer output
2019-01-18 01:00:48 +03:00
cmdShow :: IO ()
2019-01-23 23:55:26 +03:00
cmdShow = do
putStrLn $ "Showing sources file"
2019-01-23 23:55:26 +03:00
sources <- unSources <$> getSources
2019-01-23 23:55:26 +03:00
forWithKeyM_ sources $ \key (PackageSpec spec) -> do
2019-05-13 16:43:57 +03:00
T.putStrLn $ "Package: " <> unPackageName key
2019-05-14 15:36:31 +03:00
forM_ (HMS.toList spec) $ \(attrName, attrValValue) -> do
2019-01-23 23:55:26 +03:00
let attrValue = case attrValValue of
2019-01-28 23:29:35 +03:00
Aeson.String str -> str
2019-01-23 23:55:26 +03:00
_ -> "<barabajagal>"
putStrLn $ " " <> T.unpack attrName <> ": " <> T.unpack attrValue
-------------------------------------------------------------------------------
-- UPDATE
-------------------------------------------------------------------------------
2019-01-18 01:00:48 +03:00
2019-01-27 23:18:10 +03:00
parseCmdUpdate :: Opts.ParserInfo (IO ())
parseCmdUpdate =
Opts.info
2019-01-29 00:32:36 +03:00
((cmdUpdate <$> Opts.optional parsePackage) <**> Opts.helper) $
mconcat desc
where
desc =
[ Opts.fullDesc
, Opts.progDesc "Update dependencies"
, Opts.headerDoc $ Just $
"Examples:" Opts.<$$>
"" Opts.<$$>
" niv update" Opts.<$$>
" niv update nixpkgs" Opts.<$$>
" niv update my-package -v beta-0.2"
]
2019-01-24 23:58:22 +03:00
2019-06-09 23:42:35 +03:00
specToFreeAttrs :: PackageSpec -> Attrs
specToFreeAttrs = fmap (Free,) . unPackageSpec
specToLockedAttrs :: PackageSpec -> Attrs
specToLockedAttrs = fmap (Locked,) . unPackageSpec
-- TODO: sexy logging + concurrent updates
2019-01-27 23:18:10 +03:00
cmdUpdate :: Maybe (PackageName, PackageSpec) -> IO ()
cmdUpdate = \case
Just (packageName, packageSpec) -> do
2019-05-13 16:43:57 +03:00
T.putStrLn $ "Updating single package: " <> unPackageName packageName
sources <- unSources <$> getSources
2019-01-23 23:55:26 +03:00
2019-05-14 15:36:31 +03:00
packageSpec' <- case HMS.lookup packageName sources of
2019-01-27 23:18:10 +03:00
Just packageSpec' -> do
2019-06-09 23:42:35 +03:00
attrsToSpec <$> evalUpdate
(specToLockedAttrs packageSpec <> specToFreeAttrs packageSpec')
(githubUpdate nixPrefetchURL githubLatestRev githubRepo)
2019-01-30 20:50:09 +03:00
2019-01-28 23:51:10 +03:00
Nothing -> abortCannotUpdateNoSuchPackage packageName
2019-01-23 23:55:26 +03:00
setSources $ Sources $
2019-05-14 15:36:31 +03:00
HMS.insert packageName packageSpec' sources
2019-01-23 23:55:26 +03:00
2019-01-27 23:18:10 +03:00
Nothing -> do
sources <- unSources <$> getSources
2019-01-23 23:55:26 +03:00
sources' <- forWithKeyM sources $
2019-01-27 23:18:10 +03:00
\packageName packageSpec -> do
2019-05-13 16:43:57 +03:00
T.putStrLn $ "Package: " <> unPackageName packageName
2019-06-09 23:42:35 +03:00
attrsToSpec <$> evalUpdate
(specToFreeAttrs packageSpec)
(githubUpdate nixPrefetchURL githubLatestRev githubRepo)
2019-01-23 23:55:26 +03:00
setSources $ Sources sources'
2019-01-23 23:55:26 +03:00
2019-01-27 23:18:10 +03:00
-------------------------------------------------------------------------------
-- DROP
-------------------------------------------------------------------------------
2019-01-23 23:55:26 +03:00
2019-01-27 23:18:10 +03:00
parseCmdDrop :: Opts.ParserInfo (IO ())
parseCmdDrop =
Opts.info
2019-02-08 21:07:05 +03:00
((cmdDrop <$> parsePackageName <*> parseDropAttributes) <**>
Opts.helper) $
2019-01-29 00:32:36 +03:00
mconcat desc
where
desc =
[ Opts.fullDesc
, Opts.progDesc "Drop dependency"
, Opts.headerDoc $ Just $
"Examples:" Opts.<$$>
"" Opts.<$$>
2019-02-08 21:07:05 +03:00
" niv drop jq" Opts.<$$>
" niv drop my-package version"
2019-01-29 00:32:36 +03:00
]
2019-02-08 21:07:05 +03:00
parseDropAttributes :: Opts.Parser [T.Text]
parseDropAttributes = many $
Opts.argument Opts.str (Opts.metavar "ATTRIBUTE")
2019-01-27 23:18:10 +03:00
2019-02-08 21:07:05 +03:00
cmdDrop :: PackageName -> [T.Text] -> IO ()
cmdDrop packageName = \case
[] -> do
2019-05-13 16:43:57 +03:00
T.putStrLn $ "Dropping package: " <> unPackageName packageName
sources <- unSources <$> getSources
2019-01-27 23:18:10 +03:00
2019-05-14 15:36:31 +03:00
when (not $ HMS.member packageName sources) $
2019-01-28 23:51:10 +03:00
abortCannotDropNoSuchPackage packageName
2019-01-18 01:00:48 +03:00
setSources $ Sources $
2019-05-14 15:36:31 +03:00
HMS.delete packageName sources
2019-02-08 21:07:05 +03:00
attrs -> do
putStrLn $ "Dropping attributes :" <>
(T.unpack (T.intercalate " " attrs))
2019-05-13 16:43:57 +03:00
T.putStrLn $ "In package: " <> unPackageName packageName
2019-02-08 21:07:05 +03:00
sources <- unSources <$> getSources
2019-05-14 15:36:31 +03:00
packageSpec <- case HMS.lookup packageName sources of
2019-02-08 21:07:05 +03:00
Nothing ->
abortCannotAttributesDropNoSuchPackage packageName
Just (PackageSpec packageSpec) -> pure $ PackageSpec $
2019-05-14 15:36:31 +03:00
HMS.mapMaybeWithKey
2019-02-08 21:07:05 +03:00
(\k v -> if k `elem` attrs then Nothing else Just v) packageSpec
setSources $ Sources $
2019-05-14 15:36:31 +03:00
HMS.insert packageName packageSpec sources
2019-01-27 23:18:10 +03:00
2019-01-23 23:55:26 +03:00
-------------------------------------------------------------------------------
-- 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)
2019-01-28 23:29:35 +03:00
decodeFileStrict = fmap Aeson.decodeStrict . B.readFile
2019-01-23 23:55:26 +03:00
-- | 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 }
2019-01-23 23:55:26 +03:00
--- HashMap
forWithKeyM
:: (Eq k, Hashable k, Monad m)
2019-05-14 15:36:31 +03:00
=> HMS.HashMap k v1
2019-01-23 23:55:26 +03:00
-> (k -> v1 -> m v2)
2019-05-14 15:36:31 +03:00
-> m (HMS.HashMap k v2)
2019-01-23 23:55:26 +03:00
forWithKeyM = flip mapWithKeyM
forWithKeyM_
:: (Eq k, Hashable k, Monad m)
2019-05-14 15:36:31 +03:00
=> HMS.HashMap k v1
2019-01-23 23:55:26 +03:00
-> (k -> v1 -> m ())
-> m ()
forWithKeyM_ = flip mapWithKeyM_
mapWithKeyM
:: (Eq k, Hashable k, Monad m)
=> (k -> v1 -> m v2)
2019-05-14 15:36:31 +03:00
-> HMS.HashMap k v1
-> m (HMS.HashMap k v2)
2019-01-23 23:55:26 +03:00
mapWithKeyM f m = do
2019-05-14 15:36:31 +03:00
fmap mconcat $ forM (HMS.toList m) $ \(k, v) ->
HMS.singleton k <$> f k v
2019-01-23 23:55:26 +03:00
mapWithKeyM_
:: (Eq k, Hashable k, Monad m)
=> (k -> v1 -> m ())
2019-05-14 15:36:31 +03:00
-> HMS.HashMap k v1
2019-01-23 23:55:26 +03:00
-> m ()
mapWithKeyM_ f m = do
2019-05-14 15:36:31 +03:00
forM_ (HMS.toList m) $ \(k, v) ->
HMS.singleton k <$> f k v
2019-01-27 01:39:38 +03:00
2019-05-13 16:43:57 +03:00
abort :: T.Text -> IO a
2019-01-28 23:51:10 +03:00
abort msg = do
2019-05-13 16:43:57 +03:00
T.putStrLn msg
2019-01-28 23:51:10 +03:00
exitFailure
2019-06-09 23:42:35 +03:00
nixPrefetchURL :: Bool -> T.Text -> IO T.Text
nixPrefetchURL unpack (T.unpack -> url) =
lines <$> readProcess "nix-prefetch-url" args "" >>=
2019-01-29 00:32:36 +03:00
\case
2019-06-09 23:42:35 +03:00
(l:_) -> pure (T.pack l)
2019-01-29 00:32:36 +03:00
_ -> abortNixPrefetchExpectedOutput
where args = if unpack then ["--unpack", url] else [url]
2019-01-29 00:32:36 +03:00
2019-01-28 23:25:09 +03:00
-------------------------------------------------------------------------------
-- Files and their content
-------------------------------------------------------------------------------
2019-03-05 22:44:41 +03:00
-- | Checks if content is different than default and if it does /not/ contain
-- a comment line with @niv: no_update@
2019-04-07 20:56:58 +03:00
shouldUpdateNixSourcesNix :: B.ByteString -> Bool
2019-03-05 22:44:41 +03:00
shouldUpdateNixSourcesNix content =
content /= initNixSourcesNixContent &&
2019-04-07 20:56:58 +03:00
not (any lineForbids (B8.lines content))
2019-03-05 22:44:41 +03:00
where
2019-04-07 20:56:58 +03:00
lineForbids :: B8.ByteString -> Bool
2019-03-05 22:44:41 +03:00
lineForbids str =
2019-04-07 20:56:58 +03:00
case B8.uncons (B8.dropWhile isSpace str) of
Just ('#',rest) -> case B8.stripPrefix "niv:" (B8.dropWhile isSpace rest) of
Just rest' -> case B8.stripPrefix "no_update" (B8.dropWhile isSpace rest') of
Just{} -> True
2019-03-05 22:44:41 +03:00
_ -> False
_ -> False
_ -> False
warnIfOutdated :: IO ()
warnIfOutdated = do
2019-04-07 20:56:58 +03:00
tryAny (B.readFile pathNixSourcesNix) >>= \case
2019-05-13 16:43:57 +03:00
Left e -> T.putStrLn $ T.unlines
[ "Could not read " <> T.pack pathNixSourcesNix
, "Error: " <> tshow e
2019-03-05 22:44:41 +03:00
]
Right content ->
if shouldUpdateNixSourcesNix content
then
2019-05-13 16:43:57 +03:00
T.putStrLn $ T.unlines
[ "WARNING: " <> T.pack pathNixSourcesNix <> " is out of date."
2019-03-05 22:44:41 +03:00
, "Please run"
, " niv init"
2019-05-13 16:43:57 +03:00
, "or add the following line in the " <> T.pack pathNixSourcesNix <> " file:"
2019-03-05 22:44:41 +03:00
, " # niv: no_update"
]
else pure ()
2019-04-07 13:50:47 +03:00
-- | @nix/sources.nix@
pathNixSourcesNix :: FilePath
pathNixSourcesNix = "nix" </> "sources.nix"
-- | Glue code between nix and sources.json
2019-04-07 20:56:58 +03:00
initNixSourcesNixContent :: B.ByteString
initNixSourcesNixContent = $(embedFile "nix/sources.nix")
2019-01-28 23:25:09 +03:00
-- | @nix/sources.json"
pathNixSourcesJson :: FilePath
pathNixSourcesJson = "nix" </> "sources.json"
2019-01-28 23:25:09 +03:00
-- | Empty JSON map
2019-04-07 20:56:58 +03:00
initNixSourcesJsonContent :: B.ByteString
initNixSourcesJsonContent = "{}"
2019-01-28 23:51:10 +03:00
-------------------------------------------------------------------------------
-- Warn
-------------------------------------------------------------------------------
2019-04-14 15:49:13 +03:00
warnCouldNotFetchGitHubRepo :: GH.Error -> (String, String) -> IO ()
warnCouldNotFetchGitHubRepo e (owner, repo) =
putStrLn $ unlines [ line1, line2, line3 ]
where
line1 = "WARNING: Could not read from GitHub repo: " <> owner <> "/" <> repo
line2 = [s|
I assumed that your package was a GitHub repository. An error occurred while
gathering information from the repository. Check whether your package was added
correctly:
niv show
If not, try re-adding it:
niv drop <package>
niv add <package-without-typo>
Make sure the repository exists.
|]
2019-04-14 15:49:13 +03:00
line3 = unwords [ "(Error was:", show e, ")" ]
2019-01-28 23:51:10 +03:00
-------------------------------------------------------------------------------
-- Abort
-------------------------------------------------------------------------------
abortSourcesDoesntExist :: IO a
2019-06-09 19:58:30 +03:00
abortSourcesDoesntExist = abort $ T.unlines [ line1, line2 ]
where
2019-06-09 19:58:30 +03:00
line1 = "Cannot use " <> T.pack pathNixSourcesJson
line2 = [s|
The sources file does not exist! You may need to run 'niv init'.
|]
abortSourcesIsntAMap :: IO a
2019-05-13 16:43:57 +03:00
abortSourcesIsntAMap = abort $ T.unlines [ line1, line2 ]
2019-01-28 23:51:10 +03:00
where
2019-05-13 16:43:57 +03:00
line1 = "Cannot use " <> T.pack pathNixSourcesJson
2019-01-28 23:51:10 +03:00
line2 = [s|
The sources file should be a JSON map from package name to package
2019-01-28 23:51:10 +03:00
specification, e.g.:
{ ... }
|]
abortAttributeIsntAMap :: IO a
2019-05-13 16:43:57 +03:00
abortAttributeIsntAMap = abort $ T.unlines [ line1, line2 ]
2019-01-28 23:51:10 +03:00
where
2019-05-13 16:43:57 +03:00
line1 = "Cannot use " <> T.pack pathNixSourcesJson
2019-01-28 23:51:10 +03:00
line2 = [s|
The package specifications in the sources file should be JSON maps from
2019-01-28 23:51:10 +03:00
attribute name to attribute value, e.g.:
{ "nixpkgs": { "foo": "bar" } }
|]
abortSourcesIsntJSON :: IO a
2019-05-13 16:43:57 +03:00
abortSourcesIsntJSON = abort $ T.unlines [ line1, line2 ]
2019-01-28 23:51:10 +03:00
where
2019-05-13 16:43:57 +03:00
line1 = "Cannot use " <> T.pack pathNixSourcesJson
line2 = "The sources file should be JSON."
2019-01-28 23:51:10 +03:00
abortCannotAddPackageExists :: PackageName -> IO a
2019-05-13 16:43:57 +03:00
abortCannotAddPackageExists (PackageName n) = abort $ T.unlines
2019-01-28 23:51:10 +03:00
[ "Cannot add package " <> n <> "."
, "The package already exists. Use"
2019-03-28 10:43:32 +03:00
, " niv drop " <> n
2019-01-28 23:51:10 +03:00
, "and then re-add the package. Alternatively use"
2019-03-28 10:43:32 +03:00
, " niv update " <> n <> " --attr foo=bar"
2019-01-28 23:51:10 +03:00
, "to update the package's attributes."
]
abortCannotUpdateNoSuchPackage :: PackageName -> IO a
2019-05-13 16:43:57 +03:00
abortCannotUpdateNoSuchPackage (PackageName n) = abort $ T.unlines
2019-01-28 23:51:10 +03:00
[ "Cannot update package " <> n <> "."
, "The package doesn't exist. Use"
2019-03-28 10:43:32 +03:00
, " niv add " <> n
2019-01-28 23:51:10 +03:00
, "to add the package."
]
abortCannotDropNoSuchPackage :: PackageName -> IO a
2019-05-13 16:43:57 +03:00
abortCannotDropNoSuchPackage (PackageName n) = abort $ T.unlines
2019-01-28 23:51:10 +03:00
[ "Cannot drop package " <> n <> "."
, "The package doesn't exist."
]
2019-02-08 21:07:05 +03:00
abortCannotAttributesDropNoSuchPackage :: PackageName -> IO a
2019-05-13 16:43:57 +03:00
abortCannotAttributesDropNoSuchPackage (PackageName n) = abort $ T.unlines
2019-02-08 21:07:05 +03:00
[ "Cannot drop attributes of package " <> n <> "."
, "The package doesn't exist."
]
2019-01-28 23:51:10 +03:00
abortNixPrefetchExpectedOutput :: IO a
abortNixPrefetchExpectedOutput = abort [s|
Could not read the output of 'nix-prefetch-url'. This is a bug. Please create a
ticket:
https://github.com/nmattia/niv/issues/new
Thanks! I'll buy you a beer.
|]
2019-05-13 16:43:57 +03:00
tshow :: Show a => a -> T.Text
tshow = T.pack . show