update-nix-fetchgit 0.1.2.0 → 0.2.2
raw patch · 46 files changed
+2337/−363 lines, 46 filesdep +data-fixdep +directorydep +filepathdep −errorsdep −prettyprinterdep −transformersdep ~basedep ~hnixdep ~processnew-uploaderPVP ok
version bump matches the API change (PVP)
Dependencies added: data-fix, directory, filepath, github-rest, monad-validate, mtl, optparse-applicative, optparse-generic, say, syb, tasty, tasty-discover, tasty-golden, template-haskell, temporary, vector
Dependencies removed: errors, prettyprinter, transformers, uniplate
Dependency ranges changed: base, hnix, process, text
API changes (from Hackage documentation)
- Update.Nix.FetchGit: updatesFromFile :: FilePath -> [Text] -> IO (Either Warning [SpanUpdate])
- Update.Nix.FetchGit.Utils: [owner] :: RepoLocation -> Text
- Update.Nix.FetchGit.Utils: [repo] :: RepoLocation -> Text
- Update.Nix.FetchGit.Utils: extractAttr :: Text -> [Binding a] -> Either Warning a
- Update.Nix.FetchGit.Utils: findAttr :: Text -> [Binding a] -> Either Warning (Maybe a)
- Update.Nix.FetchGit.Utils: matchAttr :: Text -> Binding a -> Maybe a
+ Nix.Comments: annotateWithComments :: Vector Text -> NExprLoc -> NExprComments
+ Nix.Comments: type Comment = Text
+ Nix.Comments: type NExprComments = Fix NExprCommentsF
+ Nix.Comments: type NExprCommentsF = AnnF (Maybe Comment) NExprLocF
+ Nix.Match: Hole :: !v -> WithHoles t v
+ Nix.Match: Term :: !t (WithHoles t v) -> WithHoles t v
+ Nix.Match: addHoles :: NExpr -> WithHoles NExprF Text
+ Nix.Match: addHolesLoc :: NExprLoc -> WithHoles NExprLocF Text
+ Nix.Match: class (Traversable t, Generic1 t) => GMatchable t
+ Nix.Match: class Traversable t => Matchable t
+ Nix.Match: data WithHoles t v
+ Nix.Match: findMatches :: Matchable t => WithHoles t v -> Fix t -> [(Fix t, [(v, Fix t)])]
+ Nix.Match: gZipMatchLeft :: GMatchable t => t a -> t b -> Maybe (t (a, b))
+ Nix.Match: instance (Data.Typeable.Internal.Typeable t, Data.Data.Data (t (Nix.Match.WithHoles t v)), Data.Data.Data v) => Data.Data.Data (Nix.Match.WithHoles t v)
+ Nix.Match: instance (Nix.Match.GMatchable l, Nix.Match.GMatchable r) => Nix.Match.GMatchable (l GHC.Generics.:*: r)
+ Nix.Match: instance (Nix.Match.GMatchable l, Nix.Match.GMatchable r) => Nix.Match.GMatchable (l GHC.Generics.:+: r)
+ Nix.Match: instance (Nix.Match.Matchable a, Nix.Match.GMatchable b) => Nix.Match.GMatchable (a GHC.Generics.:.: b)
+ Nix.Match: instance (Nix.Match.Matchable f, Nix.Match.Matchable g) => Nix.Match.Matchable (Data.Functor.Compose.Compose f g)
+ Nix.Match: instance GHC.Classes.Eq a => Nix.Match.Matchable ((,) a)
+ Nix.Match: instance GHC.Classes.Eq c => Nix.Match.GMatchable (GHC.Generics.K1 m c)
+ Nix.Match: instance Nix.Match.GMatchable GHC.Generics.Par1
+ Nix.Match: instance Nix.Match.GMatchable GHC.Generics.U1
+ Nix.Match: instance Nix.Match.GMatchable t => Nix.Match.GMatchable (GHC.Generics.M1 m i t)
+ Nix.Match: instance Nix.Match.Matchable (Nix.Expr.Types.Annotated.Ann ann)
+ Nix.Match: instance Nix.Match.Matchable (Nix.Expr.Types.Antiquoted Data.Text.Internal.Text)
+ Nix.Match: instance Nix.Match.Matchable GHC.Base.NonEmpty
+ Nix.Match: instance Nix.Match.Matchable GHC.Maybe.Maybe
+ Nix.Match: instance Nix.Match.Matchable Nix.Expr.Types.Binding
+ Nix.Match: instance Nix.Match.Matchable Nix.Expr.Types.NExprF
+ Nix.Match: instance Nix.Match.Matchable Nix.Expr.Types.NKeyName
+ Nix.Match: instance Nix.Match.Matchable Nix.Expr.Types.NString
+ Nix.Match: instance Nix.Match.Matchable Nix.Expr.Types.Params
+ Nix.Match: instance Nix.Match.Matchable []
+ Nix.Match: instance Nix.Match.Matchable x => Nix.Match.GMatchable (GHC.Generics.Rec1 x)
+ Nix.Match: isOptionalPath :: NAttrPath r -> Maybe (NAttrPath r)
+ Nix.Match: match :: Matchable t => WithHoles t v -> Fix t -> Maybe [(v, Fix t)]
+ Nix.Match: zipMatchLeft :: (Matchable t, Generic1 t, GMatchable (Rep1 t)) => t a -> t b -> Maybe (t (a, b))
+ Nix.Match.Typed: TypedMatch :: [(Text, a)] -> TypedMatch (opts :: [Symbol]) (reqs :: [Symbol]) a
+ Nix.Match.Typed: TypedMatcher :: WithHoles t Text -> TypedMatcher (opts :: [Symbol]) (reqs :: [Symbol]) t
+ Nix.Match.Typed: [unTypedMatcher] :: TypedMatcher (opts :: [Symbol]) (reqs :: [Symbol]) t -> WithHoles t Text
+ Nix.Match.Typed: findMatchesTyped :: Matchable t => TypedMatcher opts reqs t -> Fix t -> [(Fix t, TypedMatch opts reqs (Fix t))]
+ Nix.Match.Typed: get :: forall x opts reqs a. (Elem "Required" x reqs, KnownSymbol x) => TypedMatch opts reqs a -> a
+ Nix.Match.Typed: getOptional :: forall x opts reqs a. (Elem "Optional" x opts, KnownSymbol x) => TypedMatch opts reqs a -> Maybe a
+ Nix.Match.Typed: matchNix :: QuasiQuoter
+ Nix.Match.Typed: matchNixLoc :: QuasiQuoter
+ Nix.Match.Typed: matchTyped :: Matchable t => TypedMatcher opts reqs t -> Fix t -> Maybe (TypedMatch opts reqs (Fix t))
+ Nix.Match.Typed: newtype TypedMatch (opts :: [Symbol]) (reqs :: [Symbol]) a
+ Nix.Match.Typed: newtype TypedMatcher (opts :: [Symbol]) (reqs :: [Symbol]) t
+ Update.Nix.FetchGit: processText :: Env -> Text -> IO Text
+ Update.Nix.FetchGit: updatesFromText :: Text -> M [SpanUpdate]
+ Update.Nix.FetchGit.Prefetch: Revision :: Text -> Revision
+ Update.Nix.FetchGit.Prefetch: [unRevision] :: Revision -> Text
+ Update.Nix.FetchGit.Prefetch: getGitFullName :: Text -> Revision -> M Text
+ Update.Nix.FetchGit.Prefetch: getGitHubRevisionDate :: Text -> Text -> Revision -> M Day
+ Update.Nix.FetchGit.Prefetch: getGitRevision :: Text -> Revision -> M Text
+ Update.Nix.FetchGit.Prefetch: newtype Revision
+ Update.Nix.FetchGit.Prefetch: nixPrefetchUrl :: [Text] -> Text -> M Text
+ Update.Nix.FetchGit.Types: Env :: (Verbosity -> Text -> IO ()) -> [(Int, Int)] -> Env
+ Update.Nix.FetchGit.Types: GitHub :: Text -> Text -> RepoLocation
+ Update.Nix.FetchGit.Types: GitLab :: Text -> Text -> RepoLocation
+ Update.Nix.FetchGit.Types: Node :: Maybe NExprLoc -> [FetchTree] -> FetchTree
+ Update.Nix.FetchGit.Types: Normal :: Verbosity
+ Update.Nix.FetchGit.Types: Quiet :: Verbosity
+ Update.Nix.FetchGit.Types: URL :: Text -> RepoLocation
+ Update.Nix.FetchGit.Types: Updater :: M (Maybe Day, [SpanUpdate]) -> Updater
+ Update.Nix.FetchGit.Types: UpdaterNode :: Updater -> FetchTree
+ Update.Nix.FetchGit.Types: Verbose :: Verbosity
+ Update.Nix.FetchGit.Types: [nodeChildren] :: FetchTree -> [FetchTree]
+ Update.Nix.FetchGit.Types: [nodeVersionExpr] :: FetchTree -> Maybe NExprLoc
+ Update.Nix.FetchGit.Types: [repoOwner] :: RepoLocation -> Text
+ Update.Nix.FetchGit.Types: [repoRepo] :: RepoLocation -> Text
+ Update.Nix.FetchGit.Types: [sayLog] :: Env -> Verbosity -> Text -> IO ()
+ Update.Nix.FetchGit.Types: [unUpdater] :: Updater -> M (Maybe Day, [SpanUpdate])
+ Update.Nix.FetchGit.Types: [updateLocations] :: Env -> [(Int, Int)]
+ Update.Nix.FetchGit.Types: asWarnings :: (Functor m, Monoid e) => ValidateT e m a -> m (e, Maybe a)
+ Update.Nix.FetchGit.Types: data Env
+ Update.Nix.FetchGit.Types: data FetchTree
+ Update.Nix.FetchGit.Types: data RepoLocation
+ Update.Nix.FetchGit.Types: data Verbosity
+ Update.Nix.FetchGit.Types: instance GHC.Show.Show Update.Nix.FetchGit.Types.RepoLocation
+ Update.Nix.FetchGit.Types: newtype Updater
+ Update.Nix.FetchGit.Types: runM :: Env -> M a -> IO ([Warning], Maybe a)
+ Update.Nix.FetchGit.Types: type M = ReaderT Env (ValidateT (Dual [Warning]) IO)
+ Update.Nix.FetchGit.Utils: [repoOwner] :: RepoLocation -> Text
+ Update.Nix.FetchGit.Utils: [repoRepo] :: RepoLocation -> Text
+ Update.Nix.FetchGit.Utils: containsPosition :: NExprLoc -> (Int, Int) -> Bool
+ Update.Nix.FetchGit.Utils: exprBool :: NExprLoc -> Either Warning Bool
+ Update.Nix.FetchGit.Utils: fromEither :: Either Warning a -> M a
+ Update.Nix.FetchGit.Utils: logNormal :: Text -> M ()
+ Update.Nix.FetchGit.Utils: logVerbose :: Text -> M ()
+ Update.Nix.FetchGit.Utils: note :: Warning -> Maybe a -> M a
+ Update.Nix.FetchGit.Utils: ourParseNixText :: Text -> Either Warning NExprLoc
+ Update.Nix.FetchGit.Utils: prettyRepoLocation :: RepoLocation -> Text
+ Update.Nix.FetchGit.Utils: refute1 :: Warning -> M a
+ Update.Nix.FetchGit.Warning: GitLsRemoteFailed :: Int -> Text -> Warning
+ Update.Nix.FetchGit.Warning: InvalidGitLsRemoteOutput :: Text -> Warning
+ Update.Nix.FetchGit.Warning: InvalidPrefetchUrlOutput :: Text -> Warning
+ Update.Nix.FetchGit.Warning: NixPrefetchUrlFailed :: Int -> Text -> Warning
+ Update.Nix.FetchGit.Warning: NoSuchRef :: Text -> Warning
+ Update.Nix.FetchGit.Warning: NotABool :: NExprLoc -> Warning
+ Update.Nix.FetchGit.Warning: instance GHC.Show.Show Update.Nix.FetchGit.Warning.Warning
+ Update.Nix.Updater: fetchers :: (NExprLoc -> Maybe Comment) -> [NExprLoc -> Maybe (M Updater)]
- Update.Nix.FetchGit: processFile :: FilePath -> [Text] -> IO ()
+ Update.Nix.FetchGit: processFile :: Env -> FilePath -> IO ()
- Update.Nix.FetchGit.Prefetch: nixPrefetchGit :: [Text] -> Text -> IO (Either Warning NixPrefetchGitOutput)
+ Update.Nix.FetchGit.Prefetch: nixPrefetchGit :: [Text] -> Text -> M NixPrefetchGitOutput
- Update.Nix.FetchGit.Utils: formatWarning :: Warning -> String
+ Update.Nix.FetchGit.Utils: formatWarning :: Warning -> Text
- Update.Nix.FetchGit.Utils: ourParseNixFile :: FilePath -> IO (Either Warning NExprLoc)
+ Update.Nix.FetchGit.Utils: ourParseNixFile :: FilePath -> M NExprLoc
- Update.Nix.FetchGit.Warning: CouldNotParseInput :: Doc Void -> Warning
+ Update.Nix.FetchGit.Warning: CouldNotParseInput :: Text -> Warning
Files
- .gitignore +23/−0
- CHANGELOG.md +30/−2
- README.md +125/−34
- app/Main.hs +98/−15
- default.nix +36/−0
- package.yaml +99/−0
- src/Nix/Comments.hs +63/−0
- src/Nix/Match.hs +296/−0
- src/Nix/Match/Typed.hs +263/−0
- src/Update/Nix/FetchGit.hs +91/−140
- src/Update/Nix/FetchGit/Prefetch.hs +142/−19
- src/Update/Nix/FetchGit/Types.hs +48/−40
- src/Update/Nix/FetchGit/Utils.hs +127/−62
- src/Update/Nix/FetchGit/Warning.hs +8/−2
- src/Update/Nix/Updater.hs +225/−0
- src/Update/Span.hs +0/−1
- tests/Driver.hs +1/−0
- tests/Samples.hs +108/−0
- tests/fakeRepo.sh +40/−0
- tests/networked/test_builtins_fetchtarball_github.expected.nix +17/−0
- tests/networked/test_builtins_fetchtarball_github.in.nix +17/−0
- tests/networked/test_github_submodules.expected.nix +21/−0
- tests/networked/test_github_submodules.in.nix +21/−0
- tests/networked/test_readme_examples.expected.nix +35/−0
- tests/networked/test_readme_examples.in.nix +35/−0
- tests/test_builtins_fetchgit.expected.nix +6/−0
- tests/test_builtins_fetchgit.in.nix +6/−0
- tests/test_builtins_fetchgit_update_ignores_sha256.expected.nix +10/−0
- tests/test_builtins_fetchgit_update_ignores_sha256.in.nix +10/−0
- tests/test_builtins_fetchtarball.expected.nix +6/−0
- tests/test_builtins_fetchtarball.in.nix +6/−0
- tests/test_dotgit.expected.nix +25/−0
- tests/test_dotgit.in.nix +25/−0
- tests/test_max_version.expected.nix +22/−0
- tests/test_max_version.in.nix +22/−0
- tests/test_rec_sets.expected.nix +9/−0
- tests/test_rec_sets.in.nix +9/−0
- tests/test_scoped.expected.nix +12/−0
- tests/test_scoped.in.nix +12/−0
- tests/test_simple_update.expected.nix +7/−0
- tests/test_simple_update.in.nix +7/−0
- tests/test_updates_with_other_errors.expected.nix +15/−0
- tests/test_updates_with_other_errors.in.nix +15/−0
- tests/test_version_update.expected.nix +8/−0
- tests/test_version_update.in.nix +8/−0
- update-nix-fetchgit.cabal +128/−48
+ .gitignore view
@@ -0,0 +1,23 @@+dist+dist-*+cabal-dev+*.o+*.hi+*.chi+*.chs.h+*.dyn_o+*.dyn_hi+.hpc+.hsenv+.cabal-sandbox/+cabal.sandbox.config+.ghc.environment.*+*.prof+*.aux+*.hp+*.eventlog+.stack-work/+cabal.project.local+*~+result+result-*
CHANGELOG.md view
@@ -1,10 +1,38 @@-# Version [0.1.2.0](https://github.com/expipiplus1/update-nix-fetchgit/compare/0.1.1.0...0.1.2.0)+# Changelog +## WIP++## [0.2.2] - 2020-11-03++- Require hnix version 0.11 with several important bugfixes++## [0.2.1] - 2020-11-01++- Add support for `callHackageDirect`++## [0.2] - 2020-10-28++- Update hashes in calls to `builtins.fetchTarball`+- Allow updating tarball urls in fetched from GitHub+- Read comments for branch/tag information+- Allow pinning revisions in tarball fetches+- Gate stderr output under `--verbose`+- Add support for `fetchSubmodules`, `leaveDotGit` and `deepClone`+- Add support for filtering update locations with `--location line:col`++### Under the Hood++- Use new unification method for Nix expressions, see ./src/Nix/Match.hs+- Rewrite matchers, now in ./src/Update/Nix/Updater.hs+- Use `monad-validate` for error handling++## Version [0.1.2.0](https://github.com/expipiplus1/update-nix-fetchgit/compare/0.1.1.0...0.1.2.0)+ * Additions * Support updating `fetchFromGitLab` * Support updating `fetchgit` and `fetchgitPrivate -# Version [0.1.1.0](https://github.com/expipiplus1/update-nix-fetchgit/compare/0.1.0.0...0.1.1.0)+## Version [0.1.1.0](https://github.com/expipiplus1/update-nix-fetchgit/compare/0.1.0.0...0.1.1.0) * Changelog started. Previous release was `0.1.0.0`.
README.md view
@@ -1,67 +1,158 @@ # update-nix-fetchgit -This is a command-line utility for updating `fetchgit`, `fetchgitPrivate`, and `fetchFromGitHub` calls in [Nix](http://nixos.org/nix/) expressions. This utility is meant to be used by people maintaining Nix expressions that fetch files from Git repositories. It automates the process of keeping such expressions up-to-date with the latest upstream sources.+This is a command-line utility for updating fetcher calls in+[Nix](http://nixos.org/nix/) expressions. It has two primary purposes: -When you run `update-nix-fetchgit` on a file, it will:+- Automating the process of keeping such expressions up-to-date with the latest+ sources -- Read the file and parse it as a Nix expression.-- Find all Git fetches (calls to `fetchgit`, `fetchgitPrivate`, `fetchFromGitHub` or `fetchFromGitLab`).-- Run [`nix-prefetch-git`](https://github.com/NixOS/nixpkgs/blob/master/pkgs/build-support/fetchgit/nix-prefetch-git) to get information about the latest HEAD commit of each repository.-- Update the corresponding rev, sha256, and version attributes for each repository.-- Overwrite the original input file.+- Filling hashes automatically instead of copying from the error message<sup>[1](#deny)</sup>. -Any `version` attribute found in the file will be updated if it is in a set that contains (directly or inderictly) a Git fetch. The version attribute will be updated to the commit date of the latest HEAD commit in the Git repository, in the time zone of the committer, in "YYYY-MM-DD" format. If the set contains multiple Git fetches, the latest such date is used.+The following fetchers are supported: -When this program fetches information from multiple repositories, it runs multiple instances of `nix-prefetch-git` in parallel.+- `fetchgit`+- `fetchgitPrivate`+- `fetchFromGitHub`+- `fetchFromGitLab`+- `builtins.fetchGit`+- `builtins.fetchTarball` (Only updates the url when this is a archive fetch+ from GitHub)+- `pkgs.haskellPackages.callHackageDirect` (Only updates the hash) +The options `deepClone`, `leaveDotGit`, `fetchSubmodules` are also supported. -# Usage+Additionally if the `rev` (for git fetches) or `url` attribute has a comment: -Pass the name of the file to be updated as the first argument:+- `pin`: the revision or URL will not be updated, only the hash will be+ changed+- Anything else: the `rev` or `url` parameter will be updated to point to the+ revision pointed to by the branch or tag named in the comment. - update-nix-fetchgit filename.nix+## Usage -The file will be updated in place.+### As a command line tool -## Extra arguments+Pass the name of the files to be updated in place or pass no files to read and+write to `stdin` and `stdout` (useful as a filter in an editor). -`update-nix-fetchgit` will pass any extra arguments after the filename to `nix-prefetch-git`:+`update-nix-fetchgit file1.nix file2.nix` - update-nix-fetchgit filename.nix --rev refs/heads/myBranch+`update-nix-fetchgit <file1.nix >file1-updated.nix` +It will update fetchers anywhere in the files, note that it is a purely+syntactic match so complicated invocations of the fetchers may not be picked+up; see <./src/Update/Nix/Updater.hs> to look at the shapes of Nix expressions+which are matched. -# Example+Please open an issue if `update-nix-fetchgit` doesn't recognize a fetcher and+you think it could. -Here is an example of a Nix expression that can be updated by this program:+### From Vim -```nix-{ stdenv, fetchgit }:+This VimScript will bind `<leader>u` to update the fetcher under the cursor. -stdenv.mkDerivation rec {- name = "foo-${version}";- version = "2016-07-13";- src = fetchgit {- url = "git://midipix.org/slibtool";- rev = "4f56fd184ef6020626492a6f954a486d54f8b7ba";- sha256 = "0nmyp5yrzl9dbq85wyiimsj9fklb8637a1936nw7zzvlnzkgh28n";- };-}+The mnemonic is `u` for "it's un*U*sual to update things in this way"++```viml+" A helper to preserve the cursor location with filters+function! Preserve(command)+ let w = winsaveview()+ execute a:command+ call winrestview(w)+endfunction++" Update fetcher under cursor, note that this might take a little while if the+" fetched path is large.+autocmd FileType nix map <nowait> <leader>u :call Preserve("%!update-nix-fetchgit --location=" . line(".") . ":" . col("."))<CR> ``` -The `rev`, `sha256`, and `version` attributes will all be updated.+[](https://asciinema.org/a/fJesaOF7jGKjYcLtUCsOqrZX6) +## Examples -# Building from source+Here are some examples of nix expressions which can be updated: -The recommended way to build this program from source for development purposes is to download and run `nix-shell` in the top-level source directory and then run `cabal build`.+- Updating `src` and `version` + ```nix+ { stdenv, fetchgit }: -# More documentation+ stdenv.mkDerivation rec {+ name = "foo-${version}";+ version = "2016-07-13";+ # ^ version will be updated to the date of the new revision+ src = fetchgit {+ url = "git://midipix.org/slibtool";+ rev = "4f56fd184ef6020626492a6f954a486d54f8b7ba";+ # ^ rev will be updated to the revision of HEAD+ sha256 = "0nmyp5yrzl9dbq85wyiimsj9fklb8637a1936nw7zzvlnzkgh28n";+ # ^ sha256 will be updated to the correct hash+ };+ }+ ``` -You can run `update-nix-fetchgit --help` or `man update-nix-fetchgit` for more documentation.+- Following a branch fetched with `builtins.fetchTarball` + ```nix+ { pkgs ? import (builtins.fetchTarball {+ url =+ "https://github.com/NixOS/nixpkgs/archive/foobar.tar.gz"; # nixos-unstable+ # ^ 'foobar' will be replaced with the revision pointed to by 'refs/heads/nixos-unstable'+ sha256 = "";+ # ^ sha256 will be updated to the correct hash+ }) { } }: + myExpression+ ```++- Updating the hash (instead of trying to build and copying the hash from the+ error message)++ ```nix+ {+ upfind = import (pkgs.fetchFromGitHub {+ owner = "expipiplus1";+ repo = "upfind";+ rev = "cb451254f5b112f839aa36e5b6fd83b60cf9b9ae"; # pin+ # ^ This will not change because of the '# pin' comment+ sha256 = _;+ # ^ This will be updated+ }) { };+ }+ ```++## Mechanism++When you run `update-nix-fetchgit` on a file, it will:++- Read the file and parse it as a Nix expression.+- Find all calls to fetchers.+- Run+ [`nix-prefetch-git`](https://github.com/NixOS/nixpkgs/blob/master/pkgs/build-support/fetchgit/nix-prefetch-git)+ or `nix-prefetch-url` (and a call to the GitHub API to get the commit date in+ the case of `builtins.fetchTarball`) to get information about the latest+ version.+- Update the corresponding `rev`, `sha256`, `url`, and `version` attributes for+ each repository.+- Overwrite the original input file or print to `stdout`++Any `version` attribute found in the file will be updated if it is in a set+that contains (directly or indirectly) a Git fetch. The version attribute will+be updated to the commit date of the latest HEAD commit in the Git repository,+in the time zone of the committer, in "YYYY-MM-DD" format. If the set contains+multiple Git fetches, the latest such date is used.++## Building from source++The recommended way to build this program from source for development purposes+is to download and run `nix-shell` in the top-level source directory and then+run `cabal build`.+ # Authors - [expipiplus1](https://github.com/expipiplus1) - I'm `jophish` on Freenode; say hi! - [DavidEGrayson](https://github.com/DavidEGrayson)++--------++<a name="deny">1</a>: Don't deny you do it
app/Main.hs view
@@ -1,20 +1,103 @@-{-# LANGUAGE LambdaCase #-} module Main where -import qualified Data.Text-import qualified System.Environment-import qualified System.Exit-import qualified Update.Nix.FetchGit-+import Data.Foldable+import qualified Data.Text.IO as T+import Data.Version ( showVersion )+import Options.Applicative+import Options.Generic+import Paths_update_nix_fetchgit ( version )+import Say+import Text.ParserCombinators.ReadP ( char+ , eof+ , readP_to_S+ , readS_to_P+ , sepBy+ )+import Update.Nix.FetchGit+import Update.Nix.FetchGit.Types main :: IO ()-main =- -- Super simple command line parsing at the moment, just look for one- -- filename and optionally pass extra arguments to `nix-prefetch-git`.- System.Environment.getArgs >>= \case- [filename] -> Update.Nix.FetchGit.processFile filename []- (filename:args) -> Update.Nix.FetchGit.processFile filename (map Data.Text.pack args)- _ -> do- putStrLn "Usage: update-nix-fetchgit filename [<extra-prefetch-args>]"- System.Exit.exitFailure+main = do+ (o, fs) <- parseOpts+ let e = env o+ let goStd = T.putStr =<< processText e =<< T.getContents+ case fs of+ [] -> goStd+ _ -> for_ fs $ \f -> if f == "-" then goStd else processFile e f++----------------------------------------------------------------+-- Env+----------------------------------------------------------------++env :: Options Unwrapped -> Env+env Options {..} =+ let sayLog+ | verbose = const sayErr+ | quiet = \case+ Verbose -> const (pure ())+ Normal -> const (pure ())+ Quiet -> sayErr+ | otherwise = \case+ Verbose -> const (pure ())+ Normal -> sayErr+ Quiet -> sayErr+ updateLocations = [ (l, c) | Position l c <- location ]+ in Env { .. }++----------------------------------------------------------------+-- Options+----------------------------------------------------------------++data Options w = Options+ { verbose :: w ::: Bool <!> "False"+ , quiet :: w ::: Bool <!> "False"+ , location :: w ::: [Position] <?> "Source location to limit updates to"+ }+ deriving stock Generic++parseOpts :: IO (Options Unwrapped, [FilePath])+parseOpts = customExecParser (prefs $ multiSuffix "...")+ (info optParser (progDesc desc))+ where+ desc = unlines+ [ "Update fetchers in Nix expressions."+ , "Without any files, stdin and stdout will be used"+ ]++optParser :: Parser (Options Unwrapped, [FilePath])+optParser =+ versionOption+ <*> ( (,)+ <$> (unwrap <$> parseRecordWithModifiers defaultModifiers+ { shortNameModifier = firstLetter+ }+ )+ <*> many+ (strArgument+ ( help "Nix files to update"+ <> Options.Applicative.metavar "FILE"+ )+ )+ )+ where+ versionString = "update-nix-fetchgit-" <> showVersion version+ versionOption :: Parser (a -> a)+ versionOption = infoOption+ versionString+ (long "version" <> help ("print " <> versionString))++instance ParseRecord (Options Wrapped)+deriving instance Show (Options Unwrapped)++data Position = Position Int Int+ deriving Show++instance Read Position where+ readsPrec _ = readP_to_S $ do+ [line, col] <- sepBy (readS_to_P reads) (char ':')+ eof+ pure $ Position line col++instance ParseField Position where+ metavar _ = "LINE:COL"
+ default.nix view
@@ -0,0 +1,36 @@+{ pkgs ? import ./nixpkgs.nix, compiler ? null, hoogle ? true+, forShell ? pkgs.lib.inNixShell }:++let+ src = pkgs.nix-gitignore.gitignoreSource [ ] ./.;++ compiler' = if compiler != null then+ compiler+ else+ "ghc" + pkgs.lib.concatStrings+ (pkgs.lib.splitVersion pkgs.haskellPackages.ghc.version);++ # Any overrides we require to the specified haskell package set+ haskellPackages = with pkgs.haskell.lib;+ pkgs.haskell.packages.${compiler'}.override {+ overrides = self: super:+ {+ data-fix = self.data-fix_0_3_0;+ optparse-generic = self.optparse-generic_1_4_4;+ optparse-applicative = self.optparse-applicative_0_16_0_0;+ } // pkgs.lib.optionalAttrs hoogle {+ ghc = super.ghc // { withPackages = super.ghc.withHoogle; };+ ghcWithPackages = self.ghc.withPackages;+ };+ };++ # Any packages to appear in the environment provisioned by nix-shell+ extraPackages = [ pkgs.git pkgs.nix pkgs.nix-prefetch-git ];+ withExtras = drv:+ drv.overrideAttrs+ (attrs: { buildInputs = attrs.buildInputs ++ extraPackages; });++ # Generate a haskell derivation using the cabal2nix tool on `update-nix-fetchgit.cabal`+ drv = haskellPackages.callCabal2nix "update-nix-fetchgit" src { };++in withExtras (if forShell then drv.env else drv)
+ package.yaml view
@@ -0,0 +1,99 @@+name: update-nix-fetchgit+version: "0.2.2"+synopsis: A program to update fetchgit values in Nix expressions+description: |+ This command-line utility is meant to be used by people maintaining Nix+ expressions that fetch files from Git repositories. It automates the process+ of keeping such expressions up-to-date with the latest upstream sources.+author: Joe Hermaszewski+maintainer: Joe Hermaszewski <haskell@monoid.al>+copyright: 2020 Joe Hermaszewski+github: expipiplus1/update-nix-fetchgit+extra-source-files:+- package.yaml+- .gitignore+- default.nix+- CHANGELOG.md+- LICENSE+- README.md+- tests/fakeRepo.sh+- tests/test_*.in.nix+- tests/test_*.expected.nix+- tests/networked/test_*.in.nix+- tests/networked/test_*.expected.nix++executables:+ update-nix-fetchgit:+ main: Main.hs+ source-dirs: app+ ghc-options: -threaded -rtsopts -with-rtsopts=-N+ dependencies:+ - base+ - optparse-applicative+ - optparse-generic >= 1.4.2+ - say+ - text >= 1.2+ - update-nix-fetchgit++library:+ source-dirs: src+ dependencies:+ - base >= 4.7 && < 5+ - aeson >= 0.9+ - async >= 2.1+ - bytestring >= 0.10+ - data-fix+ - github-rest+ - hnix >= 0.11+ - monad-validate+ - mtl+ - process >= 1.2+ - syb+ - template-haskell+ - text >= 1.2+ - time >= 1.5+ - utf8-string >= 1.0+ - vector++tests:+ update-nix-fetchgit-samples:+ source-dirs: tests+ main: Driver.hs+ dependencies:+ - base >= 4.7 && < 5+ - directory+ - filepath+ - process+ - tasty+ - tasty-discover+ - tasty-golden >= 2.3+ - temporary+ - text+ - update-nix-fetchgit++ghc-options: -Wall++default-extensions:+- DataKinds+- DefaultSignatures+- DeriveAnyClass+- DeriveDataTypeable+- DeriveGeneric+- DerivingStrategies+- FlexibleContexts+- FlexibleInstances+- GADTs+- LambdaCase+- MultiParamTypeClasses+- OverloadedStrings+- PolyKinds+- RankNTypes+- RecordWildCards+- ScopedTypeVariables+- StandaloneDeriving+- TemplateHaskellQuotes+- TupleSections+- TypeApplications+- TypeFamilies+- TypeOperators+- ViewPatterns
+ src/Nix/Comments.hs view
@@ -0,0 +1,63 @@+module Nix.Comments+ ( annotateWithComments+ , Comment+ , NExprCommentsF+ , NExprComments+ ) where++import Data.Text ( Text )+import Data.Vector ( (!?)+ , Vector+ )+import Data.Fix+import Nix.Expr+import qualified Data.Text as T+import Data.Char ( isSpace )++type Comment = Text++type NExprCommentsF = AnnF (Maybe Comment) NExprLocF++type NExprComments = Fix NExprCommentsF++-- | A comment will be added to an expression if it occurs immediately after+-- the expression in the source, i.e. on the same line with only space and ';'+-- in between.+--+-- >>> import Nix.Parser+-- >>> import Nix.Pretty+-- >>> import Data.Vector+-- >>> import Data.Foldable+-- >>> lines = T.pack <$> ["1 # foo", "+ {a=2; # bar","} # baz"]+-- >>> str = T.unlines $ lines+-- >>> Success nix = parseNixTextLoc str+-- >>> ann = annotateWithComments (fromList lines) nix+-- >>> fixUniverse e = e : (fixUniverse =<< Data.Foldable.toList (unFix e))+-- >>> pretty e@(Fix (Compose (Ann comment _)))= (prettyNix (stripAnnotation (stripAnnotation e)), comment)+-- >>> pretty <$> fixUniverse ann+-- [(1 + { a = 2; },Just "baz"),(1,Just "foo"),({ a = 2; },Just "baz"),(2,Just "bar")]+annotateWithComments :: Vector Text -> NExprLoc -> NExprComments+annotateWithComments sourceLines = go+ where+ go :: NExprLoc -> NExprComments+ go = Fix . go' . fmap go . unFix++ go' :: NExprLocF f -> NExprCommentsF f+ go' e =+ let+ comment = case spanEnd . annotation . getCompose $ e of+ SourcePos _ line col -> do+ theLine <- sourceLines !? (unPos line - 1)+ theLineAfterExpression <- dropMaybe (unPos col - 1) theLine+ let theLineAfterCruft = T.dropWhile (\c -> isSpace c || (c == ';'))+ theLineAfterExpression+ ('#', theComment) <- T.uncons theLineAfterCruft+ pure (T.strip theComment)+ in Compose (Ann comment e)++----------------------------------------------------------------+-- Utils+----------------------------------------------------------------++dropMaybe :: Int -> Text -> Maybe Text+dropMaybe i t = if T.length t >= i then Just $ T.drop i t else Nothing
+ src/Nix/Match.hs view
@@ -0,0 +1,296 @@+{-# LANGUAGE UndecidableInstances #-}+-- | A set of functions for matching on Nix expression trees and extracting the+-- values of sub-trees.+module Nix.Match+ ( match+ , findMatches+ , Matchable(..)+ , GMatchable(..)+ , WithHoles(..)+ , addHoles+ , addHolesLoc+ , isOptionalPath+ ) where++import Control.Category ( (>>>) )+import Control.Monad ( void )+import Data.Data+import Data.Fix+import Data.Foldable+import Data.List.NonEmpty ( NonEmpty )+import Data.Maybe+import Data.Monoid hiding ( All )+import Data.Text ( Text )+import qualified Data.Text as T+import GHC.Base ( NonEmpty((:|)) )+import GHC.Generics+import Nix++-- | Like 'Fix' but each layer could instead be a 'Hole'+data WithHoles t v+ = Hole !v+ | Term !(t (WithHoles t v))++deriving instance (Typeable t, Data (t (WithHoles t v)), Data v) => Data (WithHoles t v)++-- | Match a tree with holes against a tree without holes, returning the values+-- of the holes if it matches.+--+-- 'NExprF' and 'NExprLocF' are both instances of 'Matchable'. 'NExprLocF' does+-- not require the annotations to match. Please see the 'Matchable' instance+-- documentation for 'NExprF' for more details.+--+-- >>> import Nix.TH+-- >>> match (addHoles [nix|{foo = x: ^foo; bar = ^bar;}|]) [nix|{foo = x: "hello"; bar = "world"; baz = "!";}|]+-- Just [("bar",Fix (NStr (DoubleQuoted [Plain "world"]))),("foo",Fix (NStr (DoubleQuoted [Plain "hello"])))]+match :: Matchable t => WithHoles t v -> Fix t -> Maybe [(v, Fix t)]+match = fmap (`appEndo` []) .: go+ where+ go = \case+ Hole v -> \t -> Just (Endo ((v, t) :))+ Term s -> \(Fix t) -> do+ m <- zipMatchLeft s t+ fmap fold . traverse (uncurry go) . toList $ m++-- | Find all the needles in a haystack, returning the matched expression as+-- well as their filled holes. Results are returned productively in preorder.+--+-- >>> import Nix.TH+-- >>> import Control.Arrow+-- >>> pretty = prettyNix *** (fmap @[] (fmap @((,) Text) prettyNix))+-- >>> pretty <$> findMatches (addHoles [nix|{x=^x;}|]) [nix|{x=1;a={x=2;};}|]+-- [({ x = 1; a = { x = 2; }; },[("x",1)]),({ x = 2; },[("x",2)])]+findMatches+ :: Matchable t+ => WithHoles t v+ -- ^ Needle+ -> Fix t+ -- ^ Haystack+ -> [(Fix t, [(v, Fix t)])]+findMatches needle haystack =+ [ (s, r) | s <- fixUniverse haystack, Just r <- pure $ match needle s ]++-- | Get every @f@ in a @Fix f@ in preorder.+fixUniverse :: Foldable f => Fix f -> [Fix f]+fixUniverse e = e : (fixUniverse =<< toList (unFix e))++-- | Make syntactic holes into 'Hole's+addHoles :: NExpr -> WithHoles NExprF Text+addHoles = unFix >>> \case+ NSynHole n -> Hole n+ e -> Term . fmap addHoles $ e++-- | Make syntactic holes into 'Hole's+addHolesLoc :: NExprLoc -> WithHoles NExprLocF Text+addHolesLoc = unFix >>> \case+ Compose (Ann _ (NSynHole n)) -> Hole n+ e -> Term . fmap addHolesLoc $ e++----------------------------------------------------------------+-- Matchable+----------------------------------------------------------------++-- | Instances for this class can be derived for any type with a 'Generic1'+-- instance.+class Traversable t => Matchable t where+ -- | Match one level of structure, returning the matched structure with sub+ -- structures to match. Needle is the first argument, matchee is the second.+ --+ -- Unlike the @Unifiable@ class in the "unification-fd" package, this doesn't+ -- have to be a commutative operation, the needle will always be the first+ -- parameter and instances are free to treat if differently if appropriate.+ zipMatchLeft :: t a -> t b -> Maybe (t (a,b))+ default zipMatchLeft+ :: (Generic1 t, GMatchable (Rep1 t))+ => t a+ -> t b+ -> Maybe (t (a, b))+ zipMatchLeft l r = to1 <$> gZipMatchLeft (from1 l) (from1 r)++-- | Match a composition of 'Matchable' things+zipMatchLeft2+ :: (Matchable f, Matchable t) => t (f a) -> t (f b) -> Maybe (t (f (a, b)))+zipMatchLeft2 a b = zipMatchLeft a b >>= traverse (uncurry zipMatchLeft)++----------------------------------------------------------------+-- Matchable instance for NExprF and NExprLocF+----------------------------------------------------------------++-- | There are a few special cases when matching expressions to make writing+-- matchers nicer:+--+-- - For attrsets and let bindings, the matching is done on the needle's keys+-- only. i.e. the matchee may have extra keys which are ignored.+--+-- - For attrsets and let bindings, bindings which have a LHS beginning with+-- @_@ are treated as optional. If they are not present then any holes on+-- their RHS will not be filled.+--+-- - Attrsets match ignoring recursiveness+--+-- - If a function in the needle has @_@ as its parameter, it matches+-- everything, so @_@ acts as a wildcard pattern.+instance Matchable NExprF where++ zipMatchLeft (NSet _ bs1) (NSet _ bs2) = do+ (bs1', bs2') <- unzip <$> reduceBindings bs1 bs2+ to1 <$> gZipMatchLeft (from1 (NSet NNonRecursive bs1'))+ (from1 (NSet NNonRecursive bs2'))++ zipMatchLeft (NLet bs1 e1) (NLet bs2 e2) = do+ (bs1', bs2') <- unzip <$> reduceBindings bs1 bs2+ to1 <$> gZipMatchLeft (from1 (NLet bs1' e1)) (from1 (NLet bs2' e2))++ zipMatchLeft (NAbs (Param "_") e1) (NAbs _ e2) = do+ pure $ NAbs (Param "_") (e1, e2)++ zipMatchLeft l r = to1 <$> gZipMatchLeft (from1 l) (from1 r)++-- | Bindings are compared on top level structure only.+--+-- Doesn't filter bindings in the needle, as they must all be present+--+-- Bindings are returned according to their order in the needle.+--+-- Any optional (name begins with @_@) bindings may be removed from the needle.+--+-- Left hand sides are matched purely on the top level structure, this means+-- that "${a}" and "${b}" appear the same to this function, and it may not+-- match them up correctly.+reduceBindings :: [Binding q] -> [Binding r] -> Maybe [(Binding q, Binding r)]+reduceBindings needle matchee =+ let+ -- A binding is optional if the lhs starts with a '_', return the same+ -- binding but without the '_'+ isOptional = \case+ NamedVar p e l | Just p' <- isOptionalPath p -> Just (NamedVar p' e l)+ _ -> Nothing++ -- Get a representation of the left hand side which has an Eq instance+ -- This will represent some things the samelike "${a}" and "${b}"+ getLHS = \case+ NamedVar p _ _ -> Left (fmap void p)+ Inherit r ps _ -> Right (void r, fmap void ps)+ in sequence+ [ (n', ) <$> m+ | -- For each binding in the needle+ n <- needle+ , let opt = isOptional n+ -- | Use the optional demangled version if present+ n' = fromMaybe n opt+ lhs = getLHS n'+ -- Find the first matching binding in the matchee+ m = find ((lhs ==) . getLHS) matchee+ , -- Skip this element if it is not present in the matchee and is optional in the needle+ isNothing opt || isJust m+ ]++-- | Basically: does the path begin with an underscore, if so return it removed+-- without the underscore.+isOptionalPath :: NAttrPath r -> Maybe (NAttrPath r)+isOptionalPath = \case+ StaticKey n :| [] | Just ('_', t) <- T.uncons n -> Just (StaticKey t :| [])+ DynamicKey (Plain (DoubleQuoted [Plain n])) :| rs+ | Just ('_', t) <- T.uncons n -> Just+ (DynamicKey (Plain (DoubleQuoted [Plain t])) :| rs)+ _ -> Nothing++--+-- hnix types+--++instance Matchable NString where++instance Matchable (Antiquoted Text) where++-- | The matched pair uses the source location of the first argument+instance Matchable Binding where+ zipMatchLeft (NamedVar p1 v1 _) (NamedVar p2 v2 l) = do+ p <- zipMatchLeft2 p1 p2+ pure (NamedVar p (v1, v2) l)++ zipMatchLeft (Inherit x1 ys1 l) (Inherit x2 ys2 _) = do+ x <- zipMatchLeft x1 x2+ ys <- zipMatchLeft2 ys1 ys2+ pure (Inherit x ys l)++ zipMatchLeft _ _ = Nothing++-- | No Generic1 instance+instance Matchable NKeyName where+ zipMatchLeft (StaticKey k1) (StaticKey k2) | k1 == k2 = Just (StaticKey k1)+ zipMatchLeft (DynamicKey EscapedNewline) (DynamicKey EscapedNewline) =+ Just (DynamicKey EscapedNewline)+ zipMatchLeft (DynamicKey (Plain k1)) (DynamicKey (Plain k2)) = do+ k <- zipMatchLeft k1 k2+ pure $ DynamicKey (Plain k)+ zipMatchLeft (DynamicKey (Antiquoted k1)) (DynamicKey (Antiquoted k2)) =+ pure $ DynamicKey (Antiquoted (k1, k2))+ zipMatchLeft _ _ = Nothing++instance Matchable Params where++-- | Doesn't require the annotations to match, returns the second annotation.+instance Matchable (Ann ann) where+ zipMatchLeft (Ann _ a1) (Ann ann2 a2) = Just $ Ann ann2 (a1, a2)++--+-- base types+--++instance Matchable [] where++instance Matchable NonEmpty where++instance Matchable Maybe where++instance Eq a => Matchable ((,) a) where++instance (Matchable f, Matchable g)=> Matchable (Compose f g) where+++----------------------------------------------------------------+-- Generic Instance for Matchable+----------------------------------------------------------------++-- | A class used in the @default@ definition for 'zipMatchLeft'+class (Traversable t, Generic1 t) => GMatchable t where+ gZipMatchLeft :: t a -> t b -> Maybe (t (a,b))++instance GMatchable t => GMatchable (M1 m i t) where+ gZipMatchLeft (M1 l) (M1 r) = M1 <$> gZipMatchLeft l r++instance GMatchable U1 where+ gZipMatchLeft _ _ = Just U1++instance Eq c => GMatchable (K1 m c) where+ gZipMatchLeft (K1 l) (K1 r) | l == r = Just (K1 l)+ | otherwise = Nothing++instance GMatchable Par1 where+ gZipMatchLeft (Par1 l) (Par1 r) = Just . Par1 $ (l, r)++instance Matchable x => GMatchable (Rec1 x) where+ gZipMatchLeft (Rec1 l) (Rec1 r) = Rec1 <$> zipMatchLeft l r++instance (GMatchable l, GMatchable r) => GMatchable (l :+: r) where+ gZipMatchLeft (L1 l) (L1 r) = L1 <$> gZipMatchLeft l r+ gZipMatchLeft (R1 l) (R1 r) = R1 <$> gZipMatchLeft l r+ gZipMatchLeft _ _ = Nothing++instance (GMatchable l, GMatchable r) => GMatchable (l :*: r) where+ gZipMatchLeft (l1 :*: l2) (r1 :*: r2) =+ (:*:) <$> gZipMatchLeft l1 r1 <*> gZipMatchLeft l2 r2++instance (Matchable a, GMatchable b) => GMatchable (a :.: b) where+ gZipMatchLeft (Comp1 l) (Comp1 r) = do+ x <- zipMatchLeft l r >>= traverse (uncurry gZipMatchLeft)+ pure (Comp1 x)+++----------------------------------------------------------------+-- Utils+----------------------------------------------------------------++(.:) :: (b -> c) -> (a1 -> a2 -> b) -> a1 -> a2 -> c+(.:) = (.) . (.)
+ src/Nix/Match/Typed.hs view
@@ -0,0 +1,263 @@+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE TemplateHaskell #-}+-- | A more strongly typed alternative to 'Nix.Match'+module Nix.Match.Typed+ ( matchNix+ , matchNixLoc+ , TypedMatcher(..)+ , TypedMatch(..)+ , get+ , getOptional+ , matchTyped+ , findMatchesTyped+ ) where++import Control.Category ( (>>>) )+import Data.Coerce ( coerce )+import Data.Data+import Data.Fix+import Data.Generics.Aliases+import Data.Kind ( Constraint )+import Data.Maybe+import qualified Data.Text as T+import Data.Type.Equality ( type (==) )+import GHC.TypeLits ( ErrorMessage(..)+ , KnownSymbol+ , Symbol+ , TypeError+ , symbolVal+ )+import Language.Haskell.TH ( Exp(AppE, VarE)+ , ExpQ+ , Pat(..)+ , PatQ+ , Q+ , TyLit(StrTyLit)+ , Type(..)+ , appTypeE+ , litT+ , mkName+ , newName+ , strTyLit+ , tupE+ , tupP+ , varE+ , varP+ )+import Language.Haskell.TH.Lib ( appE+ , conE+ )+import Language.Haskell.TH.Quote ( QuasiQuoter(..) )+import Language.Haskell.TH.Syntax ( dataToExpQ+ , liftString+ )+import Nix hiding ( TypeError )+import Nix.Match+import Nix.TH++----------------------------------------------------------------+-- Typed matching+----------------------------------------------------------------++-- | A QuasiQuoter for safely generating 'TypedMatcher's from nix source+--+-- The expression has the type @'TypedMatcher' opts reqs 'NExprF'@ where @opts@+-- and @reqs@ are the optional and required holes from the source expression.+--+-- The pattern, if matched, will bring into scope variables named according to+-- the holes present in the expression. These will have type 'NExpr' if they+-- are required, and @Maybe 'NExpr'@ if they are optional.+--+-- This requires ViewPatterns, TypeApplications and DataKinds+--+-- >>> case [nix|{a="hello";}|] of [matchNix|{a=^a;}|] -> a+-- Fix (NStr (DoubleQuoted [Plain "hello"]))+--+-- >>> :t [matchNix|{a = ^a; b = {c = ^c; _d = ^d;};}|]+-- [matchNix|{a = ^a; b = {c = ^c; _d = ^d;};}|] :: TypedMatcher '["d"] '["a", "c"] NExprF+--+-- >>> [matchNix|let a = ^a; _b = ^b; in x|] = undefined+-- >>> :t (a, b)+-- (a, b) :: (Fix NExprF, Maybe (Fix NExprF))+matchNix :: QuasiQuoter+matchNix = QuasiQuoter { quoteExp = typedMatcherExp+ , quotePat = typedMatcherPat+ , quoteDec = error "No dec quoter for typedMatcher"+ , quoteType = error "No type quoter for typedMatcher"+ }++-- | A QuasiQuoter for safely generating 'TypedMatcher's from nix source along+-- with source location annotations+--+-- The expression has the type @'TypedMatcher' opts reqs 'NExprLocF'@ where+-- @opts@ and @reqs@ are the optional and required holes from the source+-- expression.+--+-- This requires ViewPatterns, TypeApplications and DataKinds+--+-- The pattern, if matched, will bring into scope variables named according to+-- the holes present in the expression. These will have type 'NExprLoc' if they+-- are required, and @Maybe 'NExprLoc'@ if they are optional.+matchNixLoc :: QuasiQuoter+matchNixLoc = QuasiQuoter+ { quoteExp = typedMatcherLocExp+ , quotePat = typedMatcherLocPat+ , quoteDec = error "No dec quoter for typedMatcherLoc"+ , quoteType = error "No type quoter for typedMatcherLoc"+ }++-- | A matcher with the names of the required and optional holes encoded at the+-- type level.+newtype TypedMatcher (opts :: [Symbol]) (reqs :: [Symbol]) t+ = TypedMatcher {unTypedMatcher :: WithHoles t T.Text}++-- | The results of matching with a 'TypedMatcher'. The values in the required+-- list are guaranteed to be present. The values in the optional list may be+-- present. Use 'get' and 'getOptional' to extract them safely.+newtype TypedMatch (opts :: [Symbol]) (reqs :: [Symbol]) a+ = TypedMatch [(T.Text, a)]++-- | Extract a required key from a match+get+ :: forall x opts reqs a+ . (Elem "Required" x reqs, KnownSymbol x)+ => TypedMatch opts reqs a+ -> a+get (TypedMatch ms) =+ fromMaybe (error "Required key not present in TypedMatch")+ $ lookup (T.pack (symbolVal (Proxy @x))) ms++-- | Maybe extract an optional key from a match+getOptional+ :: forall x opts reqs a+ . (Elem "Optional" x opts, KnownSymbol x)+ => TypedMatch opts reqs a+ -> Maybe a+getOptional (TypedMatch ms) = lookup (T.pack (symbolVal (Proxy @x))) ms++-- | A typed version of 'match'+matchTyped+ :: Matchable t+ => TypedMatcher opts reqs t+ -> Fix t+ -> Maybe (TypedMatch opts reqs (Fix t))+matchTyped = coerce match++-- | A typed version of 'findMatches'+findMatchesTyped+ :: Matchable t+ => TypedMatcher opts reqs t+ -> Fix t+ -> [(Fix t, TypedMatch opts reqs (Fix t))]+findMatchesTyped = coerce findMatches++typedMatcherExp :: String -> ExpQ+typedMatcherExp =+ fmap snd . typedMatcherGen parseNixText collectHoles addHoles id++typedMatcherLocExp :: String -> ExpQ+typedMatcherLocExp =+ fmap snd+ . typedMatcherGen parseNixTextLoc+ collectHolesLoc+ addHolesLoc+ stripAnnotation++typedMatcherPat :: String -> PatQ+typedMatcherPat = typedMatcherPatGen parseNixText collectHoles addHoles id++typedMatcherLocPat :: String -> PatQ+typedMatcherLocPat =+ typedMatcherPatGen parseNixTextLoc collectHolesLoc addHolesLoc stripAnnotation++typedMatcherPatGen+ :: Data a+ => (T.Text -> Result t)+ -> (t -> ([T.Text], [T.Text]))+ -> (t -> a)+ -> (t -> NExpr)+ -> String+ -> Q Pat+typedMatcherPatGen parseNix collect add strip s = do+ ((opt, req), matcher) <- typedMatcherGen parseNix collect add strip s+ -- e' <- [|fmap (\x -> $()) . matchTyped $(pure matcher)|]+ x <- newName "x"+ let pat = tupP (varP . mkName . T.unpack <$> (req <> opt))+ textSymbol = litT . strTyLit . T.unpack+ getters = tupE+ ( ((\r -> [|get @($r) $(varE x)|]) . textSymbol <$> req)+ <> ((\o -> [|getOptional @($o) $(varE x)|]) . textSymbol <$> opt)+ )+ [p|(fmap (\ $(varP x) -> $getters) . matchTyped $(pure matcher) -> Just $pat)|]++typedMatcherGen+ :: Data a+ => (T.Text -> Result t)+ -> (t -> ([T.Text], [T.Text]))+ -> (t -> a)+ -> (t -> NExpr)+ -> String+ -> Q (([T.Text], [T.Text]), Exp)+typedMatcherGen parseNix collect add strip s = do+ expr <- case parseNix (T.pack s) of+ Failure err -> fail $ show err+ Success e -> pure e+ let (opt, req) = collect expr+ optT = symbolList opt+ reqT = symbolList req+ holed = add expr+ exprExp = dataToExpQ+ ( const Nothing+ `extQ` metaExp (freeVars (strip expr))+ `extQ` (Just . liftText)+ )+ holed+ e <-+ conE 'TypedMatcher `appTypeE` pure optT `appTypeE` pure reqT `appE` exprExp+ pure ((opt, req), e)++liftText :: T.Text -> Q Exp+liftText txt = AppE (VarE 'T.pack) <$> liftString (T.unpack txt)++-- | Make a list of promoted strings+symbolList :: [T.Text] -> Type+symbolList = foldr+ (\n -> (PromotedConsT `AppT` LitT (StrTyLit (T.unpack n)) `AppT`))+ PromotedNilT++-- | Collect optional and required holes+collectHoles :: NExpr -> ([T.Text], [T.Text])+collectHoles = unFix >>> \case+ NSynHole n -> ([], [n])+ NSet _ bs -> foldMap (bindingHoles collectHoles) bs+ NLet bs e -> collectHoles e <> foldMap (bindingHoles collectHoles) bs+ e -> foldMap collectHoles e++-- | Collect optional and required holes+collectHolesLoc :: NExprLoc -> ([T.Text], [T.Text])+collectHolesLoc = unFix >>> \case+ Compose (Ann _ (NSynHole n)) -> ([], [n])+ Compose (Ann _ (NSet _ bs )) -> foldMap (bindingHoles collectHolesLoc) bs+ Compose (Ann _ (NLet bs e)) ->+ collectHolesLoc e <> foldMap (bindingHoles collectHolesLoc) bs+ e -> foldMap collectHolesLoc e++-- | Find the optional and required holees in a binding+bindingHoles :: (r -> ([a], [a])) -> Binding r -> ([a], [a])+bindingHoles f = \case+ b@(NamedVar p _ _) | isJust (isOptionalPath p) ->+ let (opt, req) = foldMap f b in (opt <> req, [])+ b -> foldMap f b++----------------------------------------------------------------+-- Helpers+----------------------------------------------------------------++type family Bool' (f :: k) (t :: k) (x :: Bool) :: k where+ Bool' f _ 'False = f+ Bool' _ t 'True = t++type family Elem n x ys :: Constraint where+ Elem n x '[] = TypeError ('Text n ':<>: 'Text " key \"" ':<>: 'Text x ':<>: 'Text "\" not found in TypedMatch")+ Elem n x (y:ys) = Bool' (Elem n x ys) (() :: Constraint) (x == y)
src/Update/Nix/FetchGit.hs view
@@ -1,163 +1,114 @@-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE QuasiQuotes #-} module Update.Nix.FetchGit- ( updatesFromFile- , processFile+ ( processFile+ , processText+ , updatesFromText ) where -import Control.Concurrent.Async (mapConcurrently)-import Control.Error-import Data.Foldable (toList)-import Data.Generics.Uniplate.Data (para)-import Data.Text (Text, pack)+import Control.Monad ( when )+import Data.Fix+import Data.Foldable+import Data.Maybe+import Data.Text ( Text+ , pack+ )+import qualified Data.Text as T+import qualified Data.Text.IO+import Data.Time ( Day )+import qualified Data.Vector as V+import Nix.Comments import Nix.Expr-import Update.Nix.FetchGit.Prefetch+import Nix.Match.Typed+import System.Exit import Update.Nix.FetchGit.Types import Update.Nix.FetchGit.Utils-import Update.Nix.FetchGit.Warning+import Update.Nix.Updater import Update.Span--import qualified Data.Text.IO-import qualified System.IO-import qualified System.Exit+import Control.Monad.Validate ( MonadValidate(tolerate) )+import Data.Functor+import Control.Monad.Reader ( MonadReader(ask) ) -------------------------------------------------------------------------------- -- Tying it all together -------------------------------------------------------------------------------- -- | Provided FilePath, update Nix file in-place-processFile :: FilePath -> [Text] -> IO ()-processFile filename args = do- t <- Data.Text.IO.readFile filename- -- Get the updates from this file.- updatesFromFile filename args >>= \case- -- If we have any errors, print them and finish.- Left ws -> printErrorAndExit ws- Right us ->- -- Update the text of the file in memory.- case updateSpans us t of- -- If updates are needed, write to the file.- t' | t' /= t -> do- Data.Text.IO.writeFile filename t'- putStrLn $ "Made " ++ (show $ length us) ++ " changes"+processFile :: Env -> FilePath -> IO ()+processFile env filename = do+ t <- Data.Text.IO.readFile filename+ t' <- processText env t+ -- If updates are needed, write to the file.+ when (t /= t') $ Data.Text.IO.writeFile filename t' - _ -> putStrLn "No updates"- where- printErrorAndExit :: Warning -> IO ()- printErrorAndExit e = do- System.IO.hPutStrLn System.IO.stderr (formatWarning e)- System.Exit.exitFailure+processText :: Env -> Text -> IO Text+processText env t = do+ (es, t') <- runM env (updatesFromText t <&> (`updateSpans` t))+ traverse_ (sayLog env Normal . formatWarning) es+ maybe exitFailure pure t' -- | Given the path to a Nix file, returns the SpanUpdates -- all the parts of the file we want to update.-updatesFromFile :: FilePath -> [Text] -> IO (Either Warning [SpanUpdate])-updatesFromFile f extraArgs = runExceptT $ do- expr <- ExceptT $ ourParseNixFile f- treeWithArgs <- hoistEither $ exprToFetchTree expr- treeWithLatest <- ExceptT $- sequenceA <$> mapConcurrently (getFetchGitLatestInfo extraArgs) treeWithArgs- pure (fetchTreeToSpanUpdates treeWithLatest)------------------------------------------------------------------------------------- Extracting information about fetches from the AST------------------------------------------------------------------------------------- Get a FetchTree from a nix expression.-exprToFetchTree :: NExprLoc -> Either Warning (FetchTree FetchGitArgs)-exprToFetchTree = para $ \e subs -> case e of- -- If it is a call (application) of fetchgit, record the- -- arguments since we will need to update them.- AnnE _ (NBinary NApp function (AnnE _ (NSet _rec bindings)))- | extractFuncName function == Just "fetchgit"- || extractFuncName function == Just "fetchgitPrivate"- -> FetchNode <$> extractFetchGitArgs bindings-- -- Similarly for builtins.fetchGit which needs special handling.- AnnE _ (NBinary NApp function (AnnE _ (NSet _rec bindings)))- | extractFuncName function == Just "fetchGit"- -> FetchNode <$> extractFetchGitBuiltinArgs bindings-- -- Also record calls to fetchFromGitHub.- AnnE _ (NBinary NApp function (AnnE _ (NSet _rec bindings)))- | extractFuncName function == Just "fetchFromGitHub"- -> FetchNode <$> extractFetchFromGitHubArgs bindings-- -- And to fetchFromGitLab.- AnnE _ (NBinary NApp function (AnnE _ (NSet _rec bindings)))- | extractFuncName function == Just "fetchFromGitLab"- -> FetchNode <$> extractFetchFromGitLabArgs bindings-- -- If it is an attribute set, find any attributes in it that we- -- might want to update.- AnnE _ (NSet _rec bindings)- -> Node <$> findAttr "version" bindings <*> sequenceA subs-- -- If this is something uninteresting, just wrap the sub-trees.- _ -> Node Nothing <$> sequenceA subs---- | Extract a 'FetchGitArgs' from the attrset being passed to fetchgit.-extractFetchGitArgs :: [Binding NExprLoc] -> Either Warning FetchGitArgs-extractFetchGitArgs bindings =- FetchGitArgs <$> (URL <$> (exprText =<< extractAttr "url" bindings))- <*> extractAttr "rev" bindings- <*> (Just <$> extractAttr "sha256" bindings)---- | Extract a 'FetchGitArgs' from the attrset being passed to builtins.fetchGit,--- unlike all the other functions it does not include a sha256 field.-extractFetchGitBuiltinArgs :: [Binding NExprLoc] -> Either Warning FetchGitArgs-extractFetchGitBuiltinArgs bindings =- FetchGitArgs <$> (URL <$> (exprText =<< extractAttr "url" bindings))- <*> extractAttr "rev" bindings- <*> pure Nothing---- | Extract a 'FetchGitArgs' from the attrset being passed to fetchFromGitHub.-extractFetchFromGitHubArgs :: [Binding NExprLoc] -> Either Warning FetchGitArgs-extractFetchFromGitHubArgs bindings =- FetchGitArgs <$> (GitHub <$> (exprText =<< extractAttr "owner" bindings)- <*> (exprText =<< extractAttr "repo" bindings))- <*> extractAttr "rev" bindings- <*> (Just <$> extractAttr "sha256" bindings)---- | Extract a 'FetchGitArgs' from the attrset being passed to fetchFromGitLab.-extractFetchFromGitLabArgs :: [Binding NExprLoc] -> Either Warning FetchGitArgs-extractFetchFromGitLabArgs bindings =- FetchGitArgs <$> (GitLab <$> (exprText =<< extractAttr "owner" bindings)- <*> (exprText =<< extractAttr "repo" bindings))- <*> extractAttr "rev" bindings- <*> (Just <$> extractAttr "sha256" bindings)+updatesFromText :: Text -> M [SpanUpdate]+updatesFromText t = do+ let nixLines = V.fromList (T.lines t)+ getComment sourceLines =+ annotation . getCompose . unFix . annotateWithComments sourceLines+ tree <- do+ expr <- fromEither $ ourParseNixText t+ findUpdates (getComment nixLines) expr+ us <- evalUpdates tree+ case us of+ [] -> logVerbose "Made no updates"+ [_] -> logVerbose "Made 1 update"+ _ -> logVerbose ("Made " <> T.pack (show (length us)) <> " updates")+ pure us ------------------------------------------------------------------------------------ Getting updated information from the internet.---------------------------------------------------------------------------------+----------------------------------------------------------------+-- Finding updates+---------------------------------------------------------------- -getFetchGitLatestInfo :: [Text] -> FetchGitArgs -> IO (Either Warning FetchGitLatestInfo)-getFetchGitLatestInfo extraArgs args = runExceptT $ do- o <- ExceptT (nixPrefetchGit extraArgs (extractUrlString $ repoLocation args))- d <- hoistEither (parseISO8601DateToDay (date o))- pure $ FetchGitLatestInfo args (rev o) (sha256 o) d+findUpdates :: (NExprLoc -> Maybe Comment) -> NExprLoc -> M FetchTree+findUpdates getComment e = do+ Env {..} <- ask+ if not (null updateLocations || any (containsPosition e) updateLocations)+ then pure $ Node Nothing []+ else+ let updaters = ($ e) <$> fetchers getComment+ in+ case asum updaters of+ Just u -> UpdaterNode <$> u+ Nothing -> case e of+ [matchNixLoc|{ _version = ^version; }|] ->+ Node version+ <$> traverse (findUpdates getComment) (toList (unFix e))+ _ -> Node Nothing+ <$> traverse (findUpdates getComment) (toList (unFix e)) ------------------------------------------------------------------------------------ Deciding which parts of the Nix file should be updated and how.---------------------------------------------------------------------------------+evalUpdates :: FetchTree -> M [SpanUpdate]+evalUpdates = fmap snd . go+ where+ go :: FetchTree -> M (Maybe Day, [SpanUpdate])+ go = \case+ UpdaterNode (Updater u) -> u+ Node versionExpr cs -> do+ (ds, ss) <- unzip . catMaybes <$> traverse (tolerate . go) cs+ -- Update version string with the maximum of versions in the children+ let latestDate = maximumMay (catMaybes ds)+ pure+ ( latestDate+ , [ SpanUpdate (exprSpan v) (quoteString . pack . show $ d)+ | Just d <- pure latestDate+ , Just v <- pure versionExpr+ ]+ <> concat ss+ ) -fetchTreeToSpanUpdates :: FetchTree FetchGitLatestInfo -> [SpanUpdate]-fetchTreeToSpanUpdates node@(Node _ cs) =- concatMap fetchTreeToSpanUpdates cs ++- toList (maybeUpdateVersion node)-fetchTreeToSpanUpdates (FetchNode f) = catMaybes [Just revUpdate, sha256Update]- where revUpdate = SpanUpdate (exprSpan (revExpr args))- (quoteString (latestRev f))- sha256Update = SpanUpdate <$> (exprSpan <$> sha256Expr args)- <*> Just (quoteString (latestSha256 f))- args = originalInfo f+----------------------------------------------------------------+-- Utils+---------------------------------------------------------------- --- Given a node of the fetch tree which might contain a version--- string, decides whether and how that version string should be--- updated. We basically just take the maximum latest commit date of--- all the fetches in the children.-maybeUpdateVersion :: FetchTree FetchGitLatestInfo -> Maybe SpanUpdate-maybeUpdateVersion node@(Node (Just versionExpr) _) = do- maxDay <- (maximumMay . fmap latestDate . toList) node- pure $ SpanUpdate (exprSpan versionExpr) ((quoteString . pack . show) maxDay)-maybeUpdateVersion _ = Nothing+maximumMay :: Ord a => [a] -> Maybe a+maximumMay = \case+ [] -> Nothing+ xs -> Just (maximum xs)
src/Update/Nix/FetchGit/Prefetch.hs view
@@ -1,19 +1,32 @@-{-# LANGUAGE DeriveAnyClass #-}-{-# LANGUAGE DeriveGeneric #-} module Update.Nix.FetchGit.Prefetch ( NixPrefetchGitOutput(..) , nixPrefetchGit+ , nixPrefetchUrl+ , getGitFullName+ , getGitRevision+ , getGitHubRevisionDate+ , Revision(..) ) where -import Control.Error-import Control.Monad.IO.Class (liftIO)-import Data.Aeson (FromJSON, decode)-import Data.ByteString.Lazy.UTF8 (fromString)-import Data.Text (Text, pack, unpack)+import Control.Monad ( guard )+import Control.Monad.IO.Class ( MonadIO(liftIO) )+import Data.Aeson ( FromJSON+ , decode+ )+import Data.ByteString.Lazy.UTF8 ( fromString )+import Data.Text ( Text+ , pack+ , unpack+ )+import qualified Data.Text as T+import Data.Time ( Day ) import GHC.Generics-import System.Exit (ExitCode (..))-import System.Process (readProcessWithExitCode)+import GitHub.REST+import System.Exit ( ExitCode(..) )+import System.Process ( readProcessWithExitCode )+import Update.Nix.FetchGit.Types+import Update.Nix.FetchGit.Utils import Update.Nix.FetchGit.Warning @@ -26,13 +39,123 @@ deriving (Show, Generic, FromJSON) -- | Run nix-prefetch-git-nixPrefetchGit :: [Text] -- ^ Extra arguments for nix-prefetch-git- -> Text -- ^ The URL to prefetch- -> IO (Either Warning NixPrefetchGitOutput)-nixPrefetchGit extraArgs prefetchURL = runExceptT $ do- (exitCode, nsStdout, nsStderr) <- liftIO $- readProcessWithExitCode "nix-prefetch-git" (map unpack extraArgs ++ [unpack prefetchURL]) ""- hoistEither $ case exitCode of- ExitFailure e -> Left (NixPrefetchGitFailed e (pack nsStderr))- ExitSuccess -> pure ()- decode (fromString nsStdout) ?? InvalidPrefetchGitOutput (pack nsStdout)+nixPrefetchGit+ :: [Text] -- ^ Extra arguments for nix-prefetch-git+ -> Text -- ^ The URL to prefetch+ -> M NixPrefetchGitOutput+nixPrefetchGit extraArgs prefetchURL = do+ (exitCode, nsStdout, nsStderr) <- liftIO $ readProcessWithExitCode+ "nix-prefetch-git"+ (map unpack extraArgs ++ [unpack prefetchURL])+ ""+ case exitCode of+ ExitFailure e -> refute1 (NixPrefetchGitFailed e (pack nsStderr))+ ExitSuccess -> pure ()+ note (InvalidPrefetchGitOutput (pack nsStdout)) (decode (fromString nsStdout))++-- | Run nix-prefetch-url --unpack+nixPrefetchUrl+ :: [Text] -- ^ Extra arguments for nix-prefetch-url+ -> Text -- ^ The URL to prefetch+ -> M Text -- The sha256 output+nixPrefetchUrl extraArgs prefetchURL = do+ (exitCode, nsStdout, nsStderr) <- liftIO $ readProcessWithExitCode+ "nix-prefetch-url"+ ("--unpack" : map unpack extraArgs ++ [unpack prefetchURL])+ ""+ case exitCode of+ ExitFailure e -> refute1 (NixPrefetchUrlFailed e (pack nsStderr))+ ExitSuccess -> pure ()+ note (InvalidPrefetchUrlOutput (pack nsStdout))+ (parseSHA256 (T.strip . T.pack $ nsStdout))++newtype Revision = Revision { unRevision :: Text }++-- | Discover if this ref is a branch or a tag+--+-- >>> runM _ $ getGitFullName "https://github.com/expipiplus1/update-nix-fetchgit" (Revision "0.1.0.0")+-- Right "refs/tags/0.1.0.0"+--+-- >>> runM _ $ getGitFullName "https://github.com/expipiplus1/update-nix-fetchgit" (Revision "joe-fetchTarball")+-- Right "refs/heads/joe-fetchTarball"+getGitFullName+ :: Text+ -- ^ git repo location+ -> Revision+ -- ^ branch or tag name+ -> M Text+ -- ^ Full name, i.e. with refs/heads/ or refs/tags/+getGitFullName repo revision = do+ (stdoutText, rs) <- gitLsRemotes repo revision+ case rs of+ [_hash, name] : _ -> pure name+ _ -> refute1 $ InvalidGitLsRemoteOutput stdoutText++-- | Return a tag or a hash+getGitRevision+ :: Text+ -- ^ git repo location+ -> Revision+ -- ^ branch or tag name+ -> M Text+ -- ^ Full name, i.e. with refs/heads/ or refs/tags/+getGitRevision repo revision = do+ (stdoutText, rs) <- gitLsRemotes repo revision+ case rs of+ [hash, name] : _ | Just tag <- stripPrefix "refs/tags/" name -> pure tag+ | otherwise -> pure hash+ _ -> refute1 $ InvalidGitLsRemoteOutput stdoutText++gitLsRemotes :: Text -> Revision -> M (Text, [[Text]])+gitLsRemotes repo revision = do+ (exitCode, nsStdout, nsStderr) <- liftIO $ readProcessWithExitCode+ "git"+ ["ls-remote", T.unpack repo, T.unpack (unRevision revision)]+ ""+ case exitCode of+ ExitFailure e -> refute1 (NixPrefetchGitFailed e (pack nsStderr))+ ExitSuccess -> pure ()+ let stdoutText = T.pack nsStdout+ case fmap T.words . T.lines $ stdoutText of+ [] -> refute1 (NoSuchRef (unRevision revision))+ rs -> pure (stdoutText, rs)++getGitHubRevisionDate :: Text -> Text -> Revision -> M Day+getGitHubRevisionDate owner repo revision = do+ dateString <- runGitHubT ghState $ do+ ref <- queryGitHub GHEndpoint+ { method = GET+ , endpoint = "/repos/:owner/:repo/commits/:ref"+ , endpointVals = [ "owner" := owner+ , "repo" := repo+ , "ref" := unRevision revision+ ]+ , ghData = []+ }+ pure $ ref .: "commit" .: "committer" .: "date"+ fromEither $ parseISO8601DateToDay dateString++ghState :: GitHubState+ghState = GitHubState { token = Nothing+ , userAgent = "expipiplus1/update-nix-fetchgit"+ , apiVersion = "v3"+ }++----------------------------------------------------------------+-- Utils+----------------------------------------------------------------++parseSHA256 :: Text -> Maybe Text+parseSHA256 t = do+ guard (base32Length == T.length t)+ guard (T.all (`elem` base32Chars) t)+ pure t+ where+ base32Chars = "0123456789abcdfghijklmnpqrsvwxyz" :: String+ sha256HashSize = 32+ base32Length = (sha256HashSize * 8 - 1) `quot` 5 + 1++stripPrefix :: Text -> Text -> Maybe Text+stripPrefix p t = if p `T.isPrefixOf` t+ then Just $ T.drop (T.length p) t+ else Nothing
src/Update/Nix/FetchGit/Types.hs view
@@ -1,53 +1,61 @@-{-# LANGUAGE DeriveDataTypeable #-}-{-# LANGUAGE DeriveTraversable #-}-{-# LANGUAGE FlexibleContexts #-} {-# OPTIONS_GHC -fno-warn-orphans #-} module Update.Nix.FetchGit.Types where -import Data.Data (Data)-import Data.Text (Text)-import qualified Data.Time (Day)-import Nix.Expr (NExprLoc)+import Control.Monad.Reader+import Control.Monad.Validate+import Control.Monad.Validate.Internal+import Data.Bifunctor ( Bifunctor(first) )+import Data.Functor+import Data.Monoid+import Data.Text ( Text )+import Data.Time ( Day )+import Nix.Expr ( NExprLoc )+import Update.Nix.FetchGit.Warning+import Update.Span --- | The day portion of a date, with no timezone information.-type Day = Data.Time.Day+type M = ReaderT Env (ValidateT (Dual [Warning]) IO) --- | A tree with a structure similar to the AST of the Nix file we are--- parsing, but which only contains the information we care about.--- The fetchInfo type parameter allows this tree to be used at--- different stages in the program where we know different amounts of--- information about a fetch expression.-data FetchTree fetchInfo = Node { nodeVersionExpr :: Maybe NExprLoc- , nodeChildren :: [FetchTree fetchInfo]- }- | FetchNode fetchInfo- deriving (Show, Data, Functor, Foldable, Traversable)+runM :: Env -> M a -> IO ([Warning], Maybe a)+runM env = fmap (first (reverse . getDual)) . asWarnings . flip runReaderT env --- | Represents the arguments to a call to fetchgit, fetchFromGitHub--- or fetchFromGitLab as parsed from a .nix file.--- sha256Expr will be empty on calls to builtins.fetchGit.-data FetchGitArgs = FetchGitArgs { repoLocation :: RepoLocation- , revExpr :: NExprLoc- , sha256Expr :: Maybe NExprLoc- }- deriving (Show, Data)+-- | Runs a 'ValidateT' computation returning the errors raised by 'refute' or+-- 'dispute' if any, as well as returning the computation’s result if possible.+asWarnings :: (Functor m, Monoid e) => ValidateT e m a -> m (e, Maybe a)+asWarnings m = unValidateT MNothing m <&> \case+ Left e -> (e, Nothing)+ Right (MJust e , a) -> (e, Just a)+ Right (MNothing, a) -> (mempty, Just a) --- | Updated information about a fetchgit call that was retrieved from--- the internet.-data FetchGitLatestInfo = FetchGitLatestInfo { originalInfo :: FetchGitArgs- , latestRev :: Text- , latestSha256 :: Text- , latestDate :: Day- }- deriving (Show, Data)+data Env = Env+ { sayLog :: Verbosity -> Text -> IO ()+ , updateLocations :: [(Int, Int)]+ } +data Verbosity+ = Verbose+ | Normal+ | Quiet++newtype Updater = Updater+ { unUpdater :: M (Maybe Day, [SpanUpdate])+ }++-- | A tree with a structure similar to the AST of the Nix file we are+-- parsing, but which only contains the information we care about.+data FetchTree+ = Node { nodeVersionExpr :: Maybe NExprLoc+ , nodeChildren :: [FetchTree]+ }+ | UpdaterNode Updater+ -- | A repo is either specified by URL or by Github owner/repo. data RepoLocation = URL Text- | GitHub { owner :: Text- , repo :: Text+ | GitHub { repoOwner :: Text+ , repoRepo :: Text }- | GitLab { owner :: Text- , repo :: Text+ | GitLab { repoOwner :: Text+ , repoRepo :: Text }- deriving (Show, Data)+ deriving Show+
src/Update/Nix/FetchGit/Utils.hs view
@@ -1,47 +1,60 @@-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE OverloadedStrings #-}-{-# LANGUAGE ViewPatterns #-} module Update.Nix.FetchGit.Utils ( RepoLocation(..)+ , ourParseNixText , ourParseNixFile , extractUrlString+ , prettyRepoLocation , quoteString , extractFuncName- , extractAttr- , findAttr- , matchAttr , exprText+ , exprBool , exprSpan+ , containsPosition , parseISO8601DateToDay , formatWarning+ , fromEither+ , note+ , refute1+ , logVerbose+ , logNormal ) where -import Data.Maybe ( catMaybes )+import Control.Monad.IO.Class ( MonadIO(liftIO) )+import Control.Monad.Reader ( MonadReader(ask) )+import Control.Monad.Validate+import Data.Fix import Data.List.NonEmpty as NE-import Data.Text ( Text- , unpack- , splitOn- )-import Data.Time ( parseTimeM- , defaultTimeLocale- )-import Nix.Parser ( parseNixFileLoc- , Result(..)- )-import Nix.Reduce-import Nix.Expr hiding ( SourcePos )+import Data.Monoid+import Data.Text ( Text+ , splitOn+ , unpack+ )+import qualified Data.Text as T+import Data.Time ( Day+ , defaultTimeLocale+ , parseTimeM+ )+import Nix.Atoms ( NAtom(NBool) )+import Nix.Expr hiding ( SourcePos )+import Nix.Parser ( Result(..)+ , parseNixFileLoc+ , parseNixTextLoc+ ) import Update.Nix.FetchGit.Types import Update.Nix.FetchGit.Warning import Update.Span -ourParseNixFile :: FilePath -> IO (Either Warning NExprLoc)-ourParseNixFile f =- parseNixFileLoc f >>= \case- Failure parseError -> pure $ Left (CouldNotParseInput parseError)- Success expr -> pure <$> reduceExpr Nothing expr+ourParseNixText :: Text -> Either Warning NExprLoc+ourParseNixText t = case parseNixTextLoc t of+ Failure parseError -> Left (CouldNotParseInput (tShow parseError))+ Success expr -> pure expr +ourParseNixFile :: FilePath -> M NExprLoc+ourParseNixFile f = liftIO (parseNixFileLoc f) >>= \case+ Failure parseError -> refute1 (CouldNotParseInput (tShow parseError))+ Success expr -> pure expr+ -- | Get the url from either a nix expression for the url or a repo and owner -- expression. extractUrlString :: RepoLocation -> Text@@ -50,6 +63,12 @@ GitHub o r -> "https://github.com/" <> o <> "/" <> r <> ".git" GitLab o r -> "https://gitlab.com/" <> o <> "/" <> r <> ".git" +prettyRepoLocation :: RepoLocation -> Text+prettyRepoLocation = \case+ URL u -> u+ GitHub o r -> o <> "/" <> r+ GitLab o r -> o <> "/" <> r+ -- Add double quotes around a string so it can be inserted into a Nix -- file as a string literal. quoteString :: Text -> Text@@ -64,6 +83,11 @@ (AnnE _ (NStr (DoubleQuoted [Plain t]))) -> pure t e -> Left (NotAString e) +exprBool :: NExprLoc -> Either Warning Bool+exprBool = \case+ (AnnE _ (NConstant (NBool b))) -> pure b+ e -> Left (NotABool e)+ -- | Get the 'SrcSpan' covering a particular expression. exprSpan :: NExprLoc -> SrcSpan exprSpan (AnnE s _) = s@@ -77,51 +101,92 @@ extractFuncName (AnnE _ (NSelect _ (NE.last -> StaticKey name) _)) = Just name extractFuncName _ = Nothing --- | Extract a named attribute from an attrset.-extractAttr :: Text -> [Binding a] -> Either Warning a-extractAttr name bs = case catMaybes (matchAttr name <$> bs) of- [x] -> pure x- [] -> Left (MissingAttr name)- _ -> Left (DuplicateAttrs name)---- | Find a named attribute in an attrset. This is appropriate for--- the case when a missing attribute is not an error.-findAttr :: Text -> [Binding a] -> Either Warning (Maybe a)-findAttr name bs = case catMaybes (matchAttr name <$> bs) of- [x] -> pure (Just x)- [] -> pure Nothing- _ -> Left (DuplicateAttrs name)---- | Returns 'Just value' if this attribute's key matches the text, otherwise--- Nothing.-matchAttr :: Text -> Binding a -> Maybe a-matchAttr t = \case- NamedVar (StaticKey t' :|[]) x _ | t == t' -> Just x- NamedVar _ _ _ -> Nothing- Inherit _ _ _ -> Nothing- -- Takes an ISO 8601 date and returns just the day portion. parseISO8601DateToDay :: Text -> Either Warning Day parseISO8601DateToDay t =- let justDate = (unpack . Prelude.head . splitOn "T") t in- case parseTimeM False defaultTimeLocale "%Y-%m-%d" justDate of- Nothing -> Left $ InvalidDateString t- Just day -> pure day+ let justDate = (unpack . Prelude.head . splitOn "T") t+ in maybe (Left $ InvalidDateString t)+ Right+ (parseTimeM False defaultTimeLocale "%Y-%m-%d" justDate) -formatWarning :: Warning -> String-formatWarning (CouldNotParseInput doc) = show doc+formatWarning :: Warning -> Text+formatWarning (CouldNotParseInput doc) = tShow doc formatWarning (MissingAttr attrName) =- "Error: The \"" <> unpack attrName <> "\" attribute is missing."+ "Error: The \"" <> attrName <> "\" attribute is missing." formatWarning (DuplicateAttrs attrName) =- "Error: The \"" <> unpack attrName <> "\" attribute appears twice in a set."+ "Error: The \"" <> attrName <> "\" attribute appears twice in a set." formatWarning (NotAString expr) = "Error: The expression at "- <> (prettyPrintSourcePos . spanBegin . exprSpan) expr- <> " is not a string literal."+ <> (T.pack . prettyPrintSourcePos . spanBegin . exprSpan) expr+ <> " is not a string literal."+formatWarning (NotABool expr) =+ "Error: The expression at "+ <> (T.pack . prettyPrintSourcePos . spanBegin . exprSpan) expr+ <> " is not a boolean literal." formatWarning (NixPrefetchGitFailed exitCode errorOutput) =- "Error: nix-prefetch-git failed with exit code " <> show exitCode- <> " and error output:\n" <> unpack errorOutput+ "Error: nix-prefetch-git failed with exit code "+ <> tShow exitCode+ <> " and error output:\n"+ <> errorOutput formatWarning (InvalidPrefetchGitOutput output) =- "Error: Output from nix-prefetch-git is invalid:\n" <> show output+ "Error: Output from nix-prefetch-git is invalid:\n" <> tShow output+formatWarning (NixPrefetchUrlFailed exitCode errorOutput) =+ "Error: nix-prefetch-url failed with exit code "+ <> tShow exitCode+ <> " and error output:\n"+ <> errorOutput+formatWarning (InvalidPrefetchUrlOutput output) =+ "Error: Output from nix-prefetch-url is invalid:\n" <> tShow output formatWarning (InvalidDateString text) =- "Error: Date string is invalid: " <> show text+ "Error: Date string is invalid: " <> tShow text+formatWarning (GitLsRemoteFailed exitCode errorOutput) =+ "Error: git ls-remote failed with exit code "+ <> tShow exitCode+ <> " and error output:\n"+ <> errorOutput+formatWarning (NoSuchRef text) = "Error: No such ref: " <> tShow text+formatWarning (InvalidGitLsRemoteOutput output) =+ "Error: Output from git ls-remote is invalid:\n" <> tShow output++tShow :: Show a => a -> Text+tShow = T.pack . show++----------------------------------------------------------------+-- Locations+----------------------------------------------------------------++containsPosition :: NExprLoc -> (Int, Int) -> Bool+containsPosition (Fix (Compose (Ann (SrcSpan begin end) _))) p =+ let unSourcePos (SourcePos _ l c) = (unPos l, unPos c)+ in p >= unSourcePos begin && p < unSourcePos end++----------------------------------------------------------------+-- Errors+----------------------------------------------------------------++fromEither :: Either Warning a -> M a+fromEither = \case+ Left e -> refute1 e+ Right a -> pure a++note :: Warning -> Maybe a -> M a+note e = \case+ Nothing -> refute1 e+ Just a -> pure a++refute1 :: Warning -> M a+refute1 = refute . Dual . pure++----------------------------------------------------------------+-- Logging+----------------------------------------------------------------++logVerbose :: Text -> M ()+logVerbose t = do+ Env{..} <- ask+ liftIO $ sayLog Verbose t++logNormal :: Text -> M ()+logNormal t = do+ Env {..} <- ask+ liftIO $ sayLog Normal t
src/Update/Nix/FetchGit/Warning.hs view
@@ -4,13 +4,19 @@ import Data.Text import Nix.Expr-import Data.Text.Prettyprint.Doc import Data.Void -data Warning = CouldNotParseInput (Doc Void)+data Warning = CouldNotParseInput Text | MissingAttr Text | DuplicateAttrs Text | NotAString NExprLoc+ | NotABool NExprLoc | NixPrefetchGitFailed Int Text | InvalidPrefetchGitOutput Text+ | NixPrefetchUrlFailed Int Text+ | InvalidPrefetchUrlOutput Text | InvalidDateString Text+ | GitLsRemoteFailed Int Text+ | NoSuchRef Text+ | InvalidGitLsRemoteOutput Text+ deriving Show
+ src/Update/Nix/Updater.hs view
@@ -0,0 +1,225 @@+{-# LANGUAGE QuasiQuotes #-}+-- | This module exports a list of 'Fetcher's, programs which match on nix+-- fetching expressions and return programs to update them+module Update.Nix.Updater+ ( fetchers+ ) where++import Data.Maybe+import Data.Text ( Text+ , splitOn+ )+import Nix ( NExprLoc )+import Nix.Comments+import Nix.Match.Typed+import qualified Update.Nix.FetchGit.Prefetch as P+import Update.Nix.FetchGit.Prefetch ( Revision(..)+ , getGitFullName+ , getGitHubRevisionDate+ , getGitRevision+ , nixPrefetchGit+ , nixPrefetchUrl+ )+import Update.Nix.FetchGit.Types+import Update.Nix.FetchGit.Utils+import Update.Span++type Fetcher = (NExprLoc -> Maybe Comment) -> NExprLoc -> Maybe (M Updater)++fetchers :: (NExprLoc -> Maybe Comment) -> [NExprLoc -> Maybe (M Updater)]+fetchers getComment =+ ($ getComment)+ <$> [ fetchgitUpdater+ , builtinsFetchGitUpdater+ , fetchTarballGithubUpdater+ , builtinsFetchTarballUpdater+ , fetchGitHubUpdater+ , hackageDirectUpdater+ ]++fetchgitUpdater :: Fetcher+fetchgitUpdater getComment = \case+ [matchNixLoc|+ ^fetcher {+ url = ^url;+ rev = ^rev; # rev+ sha256 = ^sha256;+ _deepClone = ^deepClone;+ _leaveDotGit = ^leaveDotGit;+ _fetchSubmodules = ^fetchSubmodules;+ }|] | extractFuncName fetcher `elem` [Just "fetchgit", Just "fetchgitPrivate"]+ -> Just $ do+ url' <- fromEither $ URL <$> exprText url+ let desiredRev = commentToRequest (getComment rev)+ deepClone' <- fmap (fromMaybe False) . fromEither . traverse exprBool $ deepClone+ leaveDotGit' <- fmap (fromMaybe deepClone') . fromEither . traverse exprBool $ leaveDotGit+ fetchSubmodules' <- fmap (fromMaybe True) . fromEither . traverse exprBool $ fetchSubmodules+ pure $ gitUpdater url' desiredRev deepClone' leaveDotGit' fetchSubmodules' rev (Just sha256)+ _ -> Nothing++builtinsFetchGitUpdater :: Fetcher+builtinsFetchGitUpdater getComment = \case+ [matchNixLoc|+ ^fetcher {+ url = ^url;+ rev = ^rev; # rev+ _submodules = ^submodules;+ }|] | Just "fetchGit" <- extractFuncName fetcher+ -> Just $ do+ url' <- fromEither $ URL <$> exprText url+ let desiredRev = commentToRequest (getComment rev)+ submodules' <- fmap (fromMaybe False) . fromEither . traverse exprBool $ submodules+ pure $ gitUpdater url' desiredRev False False submodules' rev Nothing+ _ -> Nothing++fetchTarballGithubUpdater :: Fetcher+fetchTarballGithubUpdater getComment = \case+ [matchNixLoc|+ ^fetcher {+ url = ^url; # rev+ sha256 = ^sha256;+ }|]+ | Just "fetchTarball" <- extractFuncName fetcher+ , Right url' <- exprText url+ , "https:" : "" : "github.com" : owner : repo : "archive" : _ <- splitOn+ "/"+ url'+ , comment <- getComment url+ , comment /= Just "pin" -- Fall back to the regular tarball updater if we've been instructed to not change this URL+ -> Just $ do+ let rev = Revision $ fromMaybe "HEAD" comment+ repoUrl = "https://github.com/" <> owner <> "/" <> repo+ pure . Updater $ do+ revision <- getGitRevision repoUrl rev+ let newUrl = repoUrl <> "/archive/" <> revision <> ".tar.gz"+ let Updater u = tarballUpdater newUrl sha256+ date <- getGitHubRevisionDate owner repo (Revision revision)+ (_, urlUpdate) <- u+ pure (Just date, SpanUpdate (exprSpan url) (quoteString newUrl) : urlUpdate)+ _ -> Nothing++builtinsFetchTarballUpdater :: Fetcher+builtinsFetchTarballUpdater _ = \case+ [matchNixLoc|+ ^fetcher {+ url = ^url;+ sha256 = ^sha256;+ }|] | Just "fetchTarball" <- extractFuncName fetcher+ -> Just $ do+ url' <- fromEither $ exprText url+ pure $ tarballUpdater url' sha256+ _ -> Nothing++fetchGitHubUpdater :: Fetcher+fetchGitHubUpdater getComment = \case+ [matchNixLoc|+ ^fetcher {+ owner = ^owner;+ repo = ^repo;+ rev = ^rev;+ sha256 = ^sha256;+ _fetchSubmodules = ^fetchSubmodules;+ }|] | Just fun <- extractFuncName fetcher >>= \case+ "fetchFromGitHub" -> Just GitHub+ "fetchFromGitLab" -> Just GitLab+ _ -> Nothing+ -> Just $ do+ owner' <- fromEither $ exprText owner+ repo' <- fromEither $ exprText repo+ let desiredRev = commentToRequest (getComment rev)+ fetchSubmodules' <- fmap (fromMaybe False) . fromEither . traverse exprBool $ fetchSubmodules+ pure $ gitUpdater (fun owner' repo') desiredRev False False fetchSubmodules' rev (Just sha256)+ _ -> Nothing++-- |+-- @+-- callHackageDirect = {pkg, ver, sha256}:+-- let pkgver = "${pkg}-${ver}";+-- in self.callCabal2nix pkg (pkgs.fetchzip {+-- url = "mirror://hackage/${pkgver}/${pkgver}.tar.gz";+-- inherit sha256;+-- });+-- @+hackageDirectUpdater :: Fetcher+hackageDirectUpdater _ = \case+ [matchNixLoc|+ ^fetcher {+ pkg = ^pkg;+ ver = ^ver;+ sha256 = ^sha256;+ }+ |] | Just "callHackageDirect" <- extractFuncName fetcher+ -> Just $ do+ pkg' <- fromEither $ exprText pkg+ ver' <- fromEither $ exprText ver+ let pkgver = pkg' <> "-" <> ver'+ url = "mirror://hackage/" <> pkgver <> "/" <> pkgver <> ".tar.gz"+ pure $ tarballUpdater url sha256+ _ -> Nothing++----------------------------------------------------------------+-- Helpers+----------------------------------------------------------------++data RevisionRequest+ = Pin+ | DoNotPin Revision++commentToRequest :: Maybe Text -> Maybe RevisionRequest+commentToRequest = \case+ Nothing -> Nothing+ Just "pin" -> Just Pin+ Just r -> Just (DoNotPin (Revision r))++gitUpdater+ :: RepoLocation+ -- ^ Repo URL+ -> Maybe RevisionRequest+ -- ^ Desired revision+ -> Bool+ -- ^ Deep Clone+ -> Bool+ -- ^ Leave .git+ -> Bool+ -- ^ Fetch submodules+ -> NExprLoc+ -- ^ rev+ -> Maybe NExprLoc+ -- ^ sha256, not present for some fetchers+ -> Updater+gitUpdater repoLocation revisionRequest deepClone leaveDotGit fetchSubmodules revExpr sha256Expr+ = Updater $ do+ let repoUrl = extractUrlString repoLocation+ logVerbose $ "Updating " <> prettyRepoLocation repoLocation+ revArgs <- case revisionRequest of+ Nothing -> pure []+ Just req -> do+ rev <- case req of+ Pin -> fromEither (exprText revExpr)+ DoNotPin r -> getGitFullName repoUrl r+ pure ["--rev", rev]+ let args =+ revArgs+ <> [ "--deepClone" | deepClone ]+ <> [ "--leave-dotGit" | leaveDotGit ]+ <> [ "--fetch-submodules" | fetchSubmodules ]+ o <- nixPrefetchGit args repoUrl+ d <- fromEither $ parseISO8601DateToDay (P.date o)+ pure+ ( Just d+ , [ SpanUpdate (exprSpan e) (quoteString (P.sha256 o))+ | Just e <- pure sha256Expr+ ]+ <> [SpanUpdate (exprSpan revExpr) (quoteString $ P.rev o)]+ )++tarballUpdater+ :: Text+ -- ^ URL+ -> NExprLoc+ -- ^ sha256+ -> Updater+tarballUpdater url sha256Expr = Updater $ do+ logVerbose $ "Updating " <> url+ sha256 <- nixPrefetchUrl [] url+ pure (Nothing, [SpanUpdate (exprSpan sha256Expr) (quoteString sha256)])
src/Update/Span.hs view
@@ -1,4 +1,3 @@-{-# LANGUAGE DeriveDataTypeable #-} -- | This module deals with updating spans of characters in values of type Text. --
+ tests/Driver.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF tasty-discover #-}
+ tests/Samples.hs view
@@ -0,0 +1,108 @@+{-# LANGUAGE OverloadedStrings #-}++module Samples where++import Test.Tasty (TestTree, testGroup)+import Test.Tasty.Golden (goldenVsFile)+import System.FilePath ((</>))+import Data.Bool (bool)+import Data.Maybe (mapMaybe)++import qualified Data.List+import qualified Data.Text+import qualified Data.Text.IO+import qualified System.Environment+import qualified System.FilePath+import qualified System.Directory+import qualified System.IO+import qualified System.IO.Temp+import qualified System.Process++import qualified Update.Nix.FetchGit+import Update.Nix.FetchGit.Types (Env(Env))+import Data.Text.IO (hPutStrLn)++-- | Provided output file @f@ pointing to e.g. @tests/test_rec_sets.out.nix@+-- * turn this into @tests/test_rec_sets.in.nix@+-- * copy to temporary directory+-- * create fake git repositories using @tests/fakeRepo.sh@+-- * adjust @url@ to point to the faked git repositories in the temporary directory+-- * perform update+-- * adjust @url@ to point to the expected one+-- * copy file back to @tests/test_rec_sets.out.nix@ so it be compared to @expected.nix@+runTest f =+ System.IO.Temp.withSystemTempDirectory "test-update-nix-fetchgit" $ \dir ->+ System.IO.Temp.withSystemTempDirectory "test-update-nix-fetchgit-store" $ \storeDir -> do+ let+ inFile = Data.Text.unpack+ $ Data.Text.replace ".out.nix" ".in.nix"+ $ Data.Text.pack f+ inBase = System.FilePath.takeBaseName inFile++ System.Directory.copyFile inFile (dir </> inBase)++ System.Directory.copyFile "tests/fakeRepo.sh" (dir </> "fakeRepo.sh")++ _ <- System.Process.readCreateProcess+ ((System.Process.shell (dir </> "fakeRepo.sh")) { System.Process.cwd = Just dir })+ mempty++ replaceFile (dir </> inBase) "/tmp/nix-update-fetchgit-test" (Data.Text.pack dir)++ System.Environment.setEnv "NIX_STATE_DIR" $ storeDir </> "state"+ System.Environment.setEnv "NIX_STORE_DIR" $ storeDir++ -- work around race condition https://github.com/NixOS/nix/issues/2706+ System.Directory.createDirectoryIfMissing True $ storeDir </> "state/gcroots"++ -- and another - error: SQLite database storeDir </> 'state/db/db.sqlite' is busy+ _ <- System.Process.readCreateProcess+ (System.Process.shell ("nix-store --init"))+ mempty++ let env = Env (const (Data.Text.IO.hPutStrLn System.IO.stderr)) []+ Update.Nix.FetchGit.processFile env (dir </> inBase)++ replaceFile (dir </> inBase) (Data.Text.pack dir) "/tmp/nix-update-fetchgit-test"++ System.Directory.copyFile (dir </> inBase) f+ where+ replaceFile f what with =+ Data.Text.IO.readFile f >>= Data.Text.IO.writeFile f . Data.Text.replace what with++test_derivation :: IO TestTree+test_derivation = do+ localSamples <- findSamples "tests"+ networkedSamples <- findSamples "tests/networked"+ samples <- inNixBuild >>= \case+ False -> pure $ localSamples <> networkedSamples+ True -> do+ hPutStrLn System.IO.stderr "Skipping networked tests inside Nix build"+ pure localSamples+ pure $ testGroup "golden" $ map mk samples+ where+ mk n =+ let tEx = (n ++ ".expected.nix")+ tOut = (n ++ ".out.nix")+ in goldenVsFile ("update of " ++ tOut) tEx tOut (runTest tOut)++findSamples :: FilePath -> IO [String]+findSamples dir =+ fmap (dir </>)+ . mapMaybe (dropSuffix ".in.nix")+ <$> System.Directory.listDirectory dir++dropSuffix :: String -> String -> Maybe String+dropSuffix s t = if s `Data.List.isSuffixOf` t+ then Just $ take (length t - length s) t+ else Nothing++-- From https://github.com/input-output-hk/ouroboros-network+-- | Infer from environment variables whether we are running within a Nix build+-- (and not just a nix-shell).+inNixBuild :: IO Bool+inNixBuild = do+ let testEnv = fmap (maybe False (not . null)) . System.Environment.lookupEnv+ haveNixBuildDir <- testEnv "NIX_BUILD_TOP"+ inNixShell <- testEnv "IN_NIX_SHELL"+ pure (haveNixBuildDir && not inNixShell)
+ tests/fakeRepo.sh view
@@ -0,0 +1,40 @@+#!/bin/sh++# Prepares git repositories on the local machine that we will use for+# testing.++set -ex++# Make sure the commit hashes are predictable.+export GIT_AUTHOR_DATE='1466974421 +0200'+export GIT_COMMITTER_DATE='1466974421 +0200'+export GIT_COMMITTER_NAME='joe'+export GIT_AUTHOR_NAME='joe'+export GIT_COMMITTER_EMAIL='joe@example.com'+export GIT_AUTHOR_EMAIL='joe@example.com'++git init repo1+cd repo1+ echo hi > test.txt+ git add test.txt+ git commit -m "initial commit"+ echo 1.0.0 > test.txt+ git commit -m "version 1.0.0" test.txt+ git tag 1.0.0+ echo '1.0.0+stuff' > test.txt+ git commit -m "added stuff" test.txt+ # Create a branch with this name+ git branch nixos-unstable+cd ..++export GIT_AUTHOR_DATE='1468031426 -0700'+export GIT_COMMITTER_DATE='1468031426 -0700'+git init repo2+cd repo2+ echo hi > test.txt+ git add test.txt+ git commit -m "initial commit"+cd ..++echo hi > test.txt+tar czvf archive.tar.gz test.txt
+ tests/networked/test_builtins_fetchtarball_github.expected.nix view
@@ -0,0 +1,17 @@+{+ # Pinning a specific revision+ # Hash will be updated+ src1 = builtins.fetchTarball {+ url =+ "https://github.com/expipiplus1/update-nix-fetchgit/archive/4ac47efd681f530baca71f1d27d43c50ba19bb72.tar.gz"; # pin+ sha256 = "1pr0r2vc0fnpk3fm17d4gnd659nkqnih1ik3bqarr3lykgg3pci6";+ };++ # Updating URL to use a tag+ # Hash will be updated+ src2 = builtins.fetchTarball {+ url =+ "https://github.com/expipiplus1/update-nix-fetchgit/archive/0.1.0.0.tar.gz"; # 0.1.0.0+ sha256 = "0zhng69b6lr8dbdwrw09glbyavw7cfqvm3gb4xqxx973iajifmv7";+ };+}
+ tests/networked/test_builtins_fetchtarball_github.in.nix view
@@ -0,0 +1,17 @@+{+ # Pinning a specific revision+ # Hash will be updated+ src1 = builtins.fetchTarball {+ url =+ "https://github.com/expipiplus1/update-nix-fetchgit/archive/4ac47efd681f530baca71f1d27d43c50ba19bb72.tar.gz"; # pin+ sha256 = "123";+ };++ # Updating URL to use a tag+ # Hash will be updated+ src2 = builtins.fetchTarball {+ url =+ "https://github.com/expipiplus1/update-nix-fetchgit/archive/gone.tar.gz"; # 0.1.0.0+ sha256 = "123";+ };+}
+ tests/networked/test_github_submodules.expected.nix view
@@ -0,0 +1,21 @@+{ pkgs ? import <nixpkgs> { } }:++with pkgs;++let+ regular = fetchFromGitHub {+ owner = "expipiplus1";+ repo = "has-submodule";+ rev = "101c71e91ec95e0dcd724b0122cf55e52aee7361"; # a-tag+ sha256 = "0km9m9krsi0pmx10qy1s5z0w4dp4h0v9xj2fgm255bdpdhm4rq55";+ };++ submodules = fetchFromGitHub {+ owner = "expipiplus1";+ repo = "has-submodule";+ rev = "101c71e91ec95e0dcd724b0122cf55e52aee7361"; # a-tag+ sha256 = "1w386008njxfwnwm4xrb3fip5wmm3nwhrfl4mb96ahhj0mcg493g";+ fetchSubmodules = true;+ };++in { inherit regular submodules;}
+ tests/networked/test_github_submodules.in.nix view
@@ -0,0 +1,21 @@+{ pkgs ? import <nixpkgs> { } }:++with pkgs;++let+ regular = fetchFromGitHub {+ owner = "expipiplus1";+ repo = "has-submodule";+ rev = "a-tag"; # a-tag+ sha256 = "";+ };++ submodules = fetchFromGitHub {+ owner = "expipiplus1";+ repo = "has-submodule";+ rev = "a-tag"; # a-tag+ sha256 = "";+ fetchSubmodules = true;+ };++in { inherit regular submodules;}
+ tests/networked/test_readme_examples.expected.nix view
@@ -0,0 +1,35 @@+{+ a = { stdenv, fetchgit }:+ stdenv.mkDerivation rec {+ name = "foo-${version}";+ version = "2016-06-26";+ # ^ version will be updated to the date of the new revision+ src = fetchgit {+ url = "/tmp/nix-update-fetchgit-test/repo1";+ rev = "1c60ae07b5740aab02e32b4f64600f002112e6fd";+ # ^ rev will be updated to the revision of HEAD+ sha256 = "008xwkjfsv3rj50x9fqj0vvggl1zwrbqh7bkvnga94krmij63hib";+ # ^ sha256 will be updated to the correct hash+ };+ };++ b = { pkgs ? import (builtins.fetchTarball {+ url =+ "https://github.com/expipiplus1/update-nix-fetchgit/archive/0.1.0.0.tar.gz"; # 0.1.0.0+ # ^ 'foobar' will be replaced with the revision pointed to by 'refs/tags/0.1.0.0'+ sha256 = "0zhng69b6lr8dbdwrw09glbyavw7cfqvm3gb4xqxx973iajifmv7";+ # ^ sha256 will be updated to the correct hash+ }) { } }:+ myExpression;++ c = {+ upfind = import (pkgs.fetchFromGitHub {+ owner = "expipiplus1";+ repo = "upfind";+ rev = "cb451254f5b112f839aa36e5b6fd83b60cf9b9ae"; # pin+ # ^ This will not change because of the '# pin' comment+ sha256 = "15g5nvs6azgb2fkdna1dxbyiabx9n63if0wcbdvs91hjafhzjaqa";+ # ^ This will be updated+ }) { };+ };+}
+ tests/networked/test_readme_examples.in.nix view
@@ -0,0 +1,35 @@+{+ a = { stdenv, fetchgit }:+ stdenv.mkDerivation rec {+ name = "foo-${version}";+ version = "2016-07-13";+ # ^ version will be updated to the date of the new revision+ src = fetchgit {+ url = "/tmp/nix-update-fetchgit-test/repo1";+ rev = "4f56fd184ef6020626492a6f954a486d54f8b7ba";+ # ^ rev will be updated to the revision of HEAD+ sha256 = "0nmyp5yrzl9dbq85wyiimsj9fklb8637a1936nw7zzvlnzkgh28n";+ # ^ sha256 will be updated to the correct hash+ };+ };++ b = { pkgs ? import (builtins.fetchTarball {+ url =+ "https://github.com/expipiplus1/update-nix-fetchgit/archive/foobar.tar.gz"; # 0.1.0.0+ # ^ 'foobar' will be replaced with the revision pointed to by 'refs/tags/0.1.0.0'+ sha256 = "";+ # ^ sha256 will be updated to the correct hash+ }) { } }:+ myExpression;++ c = {+ upfind = import (pkgs.fetchFromGitHub {+ owner = "expipiplus1";+ repo = "upfind";+ rev = "cb451254f5b112f839aa36e5b6fd83b60cf9b9ae"; # pin+ # ^ This will not change because of the '# pin' comment+ sha256 = _;+ # ^ This will be updated+ }) { };+ };+}
+ tests/test_builtins_fetchgit.expected.nix view
@@ -0,0 +1,6 @@+{+ src = builtins.fetchGit {+ url = "/tmp/nix-update-fetchgit-test/repo1";+ rev = "1c60ae07b5740aab02e32b4f64600f002112e6fd";+ };+}
+ tests/test_builtins_fetchgit.in.nix view
@@ -0,0 +1,6 @@+{+ src = builtins.fetchGit {+ url = "/tmp/nix-update-fetchgit-test/repo1";+ rev = "123";+ };+}
+ tests/test_builtins_fetchgit_update_ignores_sha256.expected.nix view
@@ -0,0 +1,10 @@+{+ src = builtins.fetchGit {+ url = "/tmp/nix-update-fetchgit-test/repo1";+ rev = "1c60ae07b5740aab02e32b4f64600f002112e6fd";+ # this is an invalid definition since fetchGit doesn't take a sha256+ # parameter, but the point of this test is to make sure the fields gets+ # ignored regardless.+ sha256 = "IGNORED";+ };+}
+ tests/test_builtins_fetchgit_update_ignores_sha256.in.nix view
@@ -0,0 +1,10 @@+{+ src = builtins.fetchGit {+ url = "/tmp/nix-update-fetchgit-test/repo1";+ rev = "123";+ # this is an invalid definition since fetchGit doesn't take a sha256+ # parameter, but the point of this test is to make sure the fields gets+ # ignored regardless.+ sha256 = "IGNORED";+ };+}
+ tests/test_builtins_fetchtarball.expected.nix view
@@ -0,0 +1,6 @@+{+ src = builtins.fetchTarball {+ url = "file:///tmp/nix-update-fetchgit-test/archive.tar.gz";+ sha256 = "1xmr8jicvzszfzpz46g37mlpvbzjl2wpwvl2b05psipssyp1sm8h";+ };+}
+ tests/test_builtins_fetchtarball.in.nix view
@@ -0,0 +1,6 @@+{+ src = builtins.fetchTarball {+ url = "file:///tmp/nix-update-fetchgit-test/archive.tar.gz";+ sha256 = "123";+ };+}
+ tests/test_dotgit.expected.nix view
@@ -0,0 +1,25 @@+{ pkgs ? import <nixpkgs> { } }:++with pkgs;++{+ src = fetchgit {+ url = "/tmp/nix-update-fetchgit-test/repo1";+ rev = "1c60ae07b5740aab02e32b4f64600f002112e6fd";+ sha256 = "008xwkjfsv3rj50x9fqj0vvggl1zwrbqh7bkvnga94krmij63hib";+ };++ srcDotGit = fetchgit {+ url = "/tmp/nix-update-fetchgit-test/repo1";+ rev = "1c60ae07b5740aab02e32b4f64600f002112e6fd";+ sha256 = "1ywf4ysn3fcr4dw24idz48i36ghvxbvi72icglp4nqhdj2s6bzj1";+ leaveDotGit = true;+ };++ srcDeep = fetchgit {+ url = "/tmp/nix-update-fetchgit-test/repo1";+ rev = "1c60ae07b5740aab02e32b4f64600f002112e6fd";+ sha256 = "0xnazgn7p7xspda2wsfk1v1xrw9z5vvban0nhwd7xd3f70223gzz";+ deepClone = true;+ };+}
+ tests/test_dotgit.in.nix view
@@ -0,0 +1,25 @@+{ pkgs ? import <nixpkgs> { } }:++with pkgs;++{+ src = fetchgit {+ url = "/tmp/nix-update-fetchgit-test/repo1";+ rev = "123";+ sha256 = "beef";+ };++ srcDotGit = fetchgit {+ url = "/tmp/nix-update-fetchgit-test/repo1";+ rev = "123";+ sha256 = "beef";+ leaveDotGit = true;+ };++ srcDeep = fetchgit {+ url = "/tmp/nix-update-fetchgit-test/repo1";+ rev = "123";+ sha256 = "beef";+ deepClone = true;+ };+}
+ tests/test_max_version.expected.nix view
@@ -0,0 +1,22 @@+{+ version = "2016-07-08";+ repos = {+ version = "2016-07-08";+ repo1 = {+ version = "2016-06-26";+ src = fetchgit {+ url = "/tmp/nix-update-fetchgit-test/repo1";+ rev = "1c60ae07b5740aab02e32b4f64600f002112e6fd";+ sha256 = "008xwkjfsv3rj50x9fqj0vvggl1zwrbqh7bkvnga94krmij63hib";+ };+ };+ repo2 = {+ version = "2016-07-08";+ src = fetchgit {+ url = "/tmp/nix-update-fetchgit-test/repo2";+ rev = "1ac598e1fd0ec3556f1349bb5fd8d08d89580c8a";+ sha256 = "0fbv29f0qi6kjvjd9ancw80qhvj4ylv8jmr726mj001q4vacjs18";+ };+ };+ };+}
+ tests/test_max_version.in.nix view
@@ -0,0 +1,22 @@+{+ version = "2016-01-01";+ repos = {+ version = "2016-01-01";+ repo1 = {+ version = "2016-01-01";+ src = fetchgit {+ url = "/tmp/nix-update-fetchgit-test/repo1";+ rev = "123";+ sha256 = "beef";+ };+ };+ repo2 = {+ version = "2016-01-01";+ src = fetchgit {+ url = "/tmp/nix-update-fetchgit-test/repo2";+ rev = "456";+ sha256 = "feeb";+ };+ };+ };+}
+ tests/test_rec_sets.expected.nix view
@@ -0,0 +1,9 @@+rec {+ name = "foobar-${version}";+ version = "2016-06-26";+ src = fetchgit rec {+ url = "/tmp/nix-update-fetchgit-test/repo1";+ rev = "1c60ae07b5740aab02e32b4f64600f002112e6fd";+ sha256 = "008xwkjfsv3rj50x9fqj0vvggl1zwrbqh7bkvnga94krmij63hib";+ };+}
+ tests/test_rec_sets.in.nix view
@@ -0,0 +1,9 @@+rec {+ name = "foobar-${version}";+ version = "2016-01-01";+ src = fetchgit rec {+ url = "/tmp/nix-update-fetchgit-test/repo1";+ rev = "123";+ sha256 = "beef";+ };+}
+ tests/test_scoped.expected.nix view
@@ -0,0 +1,12 @@+{+ src = pkgs.fetchgit {+ url = "/tmp/nix-update-fetchgit-test/repo1";+ rev = "1c60ae07b5740aab02e32b4f64600f002112e6fd";+ sha256 = "008xwkjfsv3rj50x9fqj0vvggl1zwrbqh7bkvnga94krmij63hib";+ };+ src = foo.bar.pkgs.fetchgit {+ url = "/tmp/nix-update-fetchgit-test/repo1";+ rev = "1c60ae07b5740aab02e32b4f64600f002112e6fd";+ sha256 = "008xwkjfsv3rj50x9fqj0vvggl1zwrbqh7bkvnga94krmij63hib";+ };+}
+ tests/test_scoped.in.nix view
@@ -0,0 +1,12 @@+{+ src = pkgs.fetchgit {+ url = "/tmp/nix-update-fetchgit-test/repo1";+ rev = "123";+ sha256 = "beef";+ };+ src = foo.bar.pkgs.fetchgit {+ url = "/tmp/nix-update-fetchgit-test/repo1";+ rev = "123";+ sha256 = "beef";+ };+}
+ tests/test_simple_update.expected.nix view
@@ -0,0 +1,7 @@+{+ src = fetchgit {+ url = "/tmp/nix-update-fetchgit-test/repo1";+ rev = "1c60ae07b5740aab02e32b4f64600f002112e6fd";+ sha256 = "008xwkjfsv3rj50x9fqj0vvggl1zwrbqh7bkvnga94krmij63hib";+ };+}
+ tests/test_simple_update.in.nix view
@@ -0,0 +1,7 @@+{+ src = fetchgit {+ url = "/tmp/nix-update-fetchgit-test/repo1";+ rev = "123";+ sha256 = "beef";+ };+}
+ tests/test_updates_with_other_errors.expected.nix view
@@ -0,0 +1,15 @@+{+ version = "2016-06-26";++ should-error = fetchgit rec {+ url = "/tmp/nix-update-fetchgit-test/missingRepo";+ rev = "123";+ sha256 = "beef";+ };++ should-update = fetchgit rec {+ url = "/tmp/nix-update-fetchgit-test/repo1";+ rev = "1c60ae07b5740aab02e32b4f64600f002112e6fd";+ sha256 = "008xwkjfsv3rj50x9fqj0vvggl1zwrbqh7bkvnga94krmij63hib";+ };+}
+ tests/test_updates_with_other_errors.in.nix view
@@ -0,0 +1,15 @@+{+ version = _;++ should-error = fetchgit rec {+ url = "/tmp/nix-update-fetchgit-test/missingRepo";+ rev = "123";+ sha256 = "beef";+ };++ should-update = fetchgit rec {+ url = "/tmp/nix-update-fetchgit-test/repo1";+ rev = "123";+ sha256 = "beef";+ };+}
+ tests/test_version_update.expected.nix view
@@ -0,0 +1,8 @@+{+ version = "2016-06-26";+ src = fetchgit {+ url = "/tmp/nix-update-fetchgit-test/repo1";+ rev = "1c60ae07b5740aab02e32b4f64600f002112e6fd";+ sha256 = "008xwkjfsv3rj50x9fqj0vvggl1zwrbqh7bkvnga94krmij63hib";+ };+}
+ tests/test_version_update.in.nix view
@@ -0,0 +1,8 @@+{+ version = "2016-01-01";+ src = fetchgit {+ url = "/tmp/nix-update-fetchgit-test/repo1";+ rev = "123";+ sha256 = "beef";+ };+}
update-nix-fetchgit.cabal view
@@ -1,55 +1,135 @@-name: update-nix-fetchgit-version: 0.1.2.0-synopsis: A program to update fetchgit values in Nix expressions-description: This command-line utility is meant to be used by people maintaining Nix expressions that fetch files from Git repositories.- It automates the process of keeping such expressions up-to-date with the latest upstream sources.-homepage: https://github.com/expipiplus1/update-nix-fetchgit#readme-license: BSD3-license-file: LICENSE-author: Joe Hermaszewski-maintainer: haskell@monoid.al-copyright: 2015 Joe Hermaszewski-category: Nix-build-type: Simple-cabal-version: >=1.10+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.34.2.+--+-- see: https://github.com/sol/hpack++name: update-nix-fetchgit+version: 0.2.2+synopsis: A program to update fetchgit values in Nix expressions+description: This command-line utility is meant to be used by people maintaining Nix+ expressions that fetch files from Git repositories. It automates the process+ of keeping such expressions up-to-date with the latest upstream sources.+homepage: https://github.com/expipiplus1/update-nix-fetchgit#readme+bug-reports: https://github.com/expipiplus1/update-nix-fetchgit/issues+author: Joe Hermaszewski+maintainer: Joe Hermaszewski <haskell@monoid.al>+copyright: 2020 Joe Hermaszewski+license: BSD3+license-file: LICENSE+build-type: Simple extra-source-files:- CHANGELOG.md- LICENSE- README.md+ package.yaml+ .gitignore+ default.nix+ CHANGELOG.md+ LICENSE+ README.md+ tests/fakeRepo.sh+ tests/test_builtins_fetchgit.in.nix+ tests/test_builtins_fetchgit_update_ignores_sha256.in.nix+ tests/test_builtins_fetchtarball.in.nix+ tests/test_dotgit.in.nix+ tests/test_max_version.in.nix+ tests/test_rec_sets.in.nix+ tests/test_scoped.in.nix+ tests/test_simple_update.in.nix+ tests/test_updates_with_other_errors.in.nix+ tests/test_version_update.in.nix+ tests/test_builtins_fetchgit.expected.nix+ tests/test_builtins_fetchgit_update_ignores_sha256.expected.nix+ tests/test_builtins_fetchtarball.expected.nix+ tests/test_dotgit.expected.nix+ tests/test_max_version.expected.nix+ tests/test_rec_sets.expected.nix+ tests/test_scoped.expected.nix+ tests/test_simple_update.expected.nix+ tests/test_updates_with_other_errors.expected.nix+ tests/test_version_update.expected.nix+ tests/networked/test_builtins_fetchtarball_github.in.nix+ tests/networked/test_github_submodules.in.nix+ tests/networked/test_readme_examples.in.nix+ tests/networked/test_builtins_fetchtarball_github.expected.nix+ tests/networked/test_github_submodules.expected.nix+ tests/networked/test_readme_examples.expected.nix +source-repository head+ type: git+ location: https://github.com/expipiplus1/update-nix-fetchgit+ library- hs-source-dirs: src- exposed-modules: Update.Nix.FetchGit- , Update.Span- , Update.Nix.FetchGit.Prefetch- , Update.Nix.FetchGit.Warning- , Update.Nix.FetchGit.Utils- other-modules: Update.Nix.FetchGit.Types- build-depends: base >= 4.7 && < 5- , aeson >= 0.9- , async >= 2.1- , bytestring >= 0.10- , errors >= 2.1- , hnix >= 0.8- , prettyprinter- , process >= 1.2- , text >= 1.2- , time >= 1.5- , transformers >= 0.4- , uniplate >= 1.6- , utf8-string >= 1.0- default-language: Haskell2010+ exposed-modules:+ Nix.Comments+ Nix.Match+ Nix.Match.Typed+ Update.Nix.FetchGit+ Update.Nix.FetchGit.Prefetch+ Update.Nix.FetchGit.Types+ Update.Nix.FetchGit.Utils+ Update.Nix.FetchGit.Warning+ Update.Nix.Updater+ Update.Span+ other-modules:+ Paths_update_nix_fetchgit+ hs-source-dirs:+ src+ default-extensions: DataKinds DefaultSignatures DeriveAnyClass DeriveDataTypeable DeriveGeneric DerivingStrategies FlexibleContexts FlexibleInstances GADTs LambdaCase MultiParamTypeClasses OverloadedStrings PolyKinds RankNTypes RecordWildCards ScopedTypeVariables StandaloneDeriving TemplateHaskellQuotes TupleSections TypeApplications TypeFamilies TypeOperators ViewPatterns ghc-options: -Wall+ build-depends:+ aeson >=0.9+ , async >=2.1+ , base >=4.7 && <5+ , bytestring >=0.10+ , data-fix+ , github-rest+ , hnix >=0.11+ , monad-validate+ , mtl+ , process >=1.2+ , syb+ , template-haskell+ , text >=1.2+ , time >=1.5+ , utf8-string >=1.0+ , vector+ default-language: Haskell2010 executable update-nix-fetchgit- hs-source-dirs: app- main-is: Main.hs- ghc-options: -threaded -rtsopts -with-rtsopts=-N -Wall- build-depends: base- , text >= 1.2- , update-nix-fetchgit- default-language: Haskell2010+ main-is: Main.hs+ other-modules:+ Paths_update_nix_fetchgit+ hs-source-dirs:+ app+ default-extensions: DataKinds DefaultSignatures DeriveAnyClass DeriveDataTypeable DeriveGeneric DerivingStrategies FlexibleContexts FlexibleInstances GADTs LambdaCase MultiParamTypeClasses OverloadedStrings PolyKinds RankNTypes RecordWildCards ScopedTypeVariables StandaloneDeriving TemplateHaskellQuotes TupleSections TypeApplications TypeFamilies TypeOperators ViewPatterns+ ghc-options: -Wall -threaded -rtsopts -with-rtsopts=-N+ build-depends:+ base+ , optparse-applicative+ , optparse-generic >=1.4.2+ , say+ , text >=1.2+ , update-nix-fetchgit+ default-language: Haskell2010 -source-repository head- type: git- location: https://github.com/expipiplus1/update-nix-fetchgit+test-suite update-nix-fetchgit-samples+ type: exitcode-stdio-1.0+ main-is: Driver.hs+ other-modules:+ Samples+ Paths_update_nix_fetchgit+ hs-source-dirs:+ tests+ default-extensions: DataKinds DefaultSignatures DeriveAnyClass DeriveDataTypeable DeriveGeneric DerivingStrategies FlexibleContexts FlexibleInstances GADTs LambdaCase MultiParamTypeClasses OverloadedStrings PolyKinds RankNTypes RecordWildCards ScopedTypeVariables StandaloneDeriving TemplateHaskellQuotes TupleSections TypeApplications TypeFamilies TypeOperators ViewPatterns+ ghc-options: -Wall+ build-depends:+ base >=4.7 && <5+ , directory+ , filepath+ , process+ , tasty+ , tasty-discover+ , tasty-golden >=2.3+ , temporary+ , text+ , update-nix-fetchgit+ default-language: Haskell2010