From a18ffa0ad484ffa033e74a3ed133e755fff97c39 Mon Sep 17 00:00:00 2001 From: teggotic Date: Thu, 8 Sep 2022 20:19:07 +0300 Subject: [PATCH] Start porting to haskell The `show-work` command is migrated to Haskell with tests. --- .../workflows/haskell-quality-pipeline.yml | 53 + .github/workflows/main.yml | 9 - .gitignore | 3 + .hlint.yaml | 1036 +++++++++++++++++ .workflows/save-work-after | 2 +- .workflows/save-work-ahead | 4 +- Setup.hs | 2 + app/Main.hs | 7 + elegant-git.cabal | 121 ++ elegant-git.iml | 14 + hie.yaml | 13 + package.yaml | 79 ++ src/Elegit/Cli/Action/ShowWork.hs | 39 + src/Elegit/Cli/Command.hs | 4 + src/Elegit/Cli/Parser.hs | 26 + src/Elegit/Git/Action.hs | 139 +++ src/Elegit/Git/Runner/Real.hs | 69 ++ src/Elegit/Git/Runner/Simulated.hs | 154 +++ src/Lib.hs | 21 + stack.yaml | 67 ++ stack.yaml.lock | 13 + test/Elegit/Cli/Action/ShowWorkSpec.hs | 105 ++ test/Elegit/Git/Runner/SimulatedSpec.hs | 20 + test/Spec.hs | 1 + 24 files changed, 1990 insertions(+), 11 deletions(-) create mode 100644 .github/workflows/haskell-quality-pipeline.yml delete mode 100644 .github/workflows/main.yml create mode 100644 .hlint.yaml create mode 100644 Setup.hs create mode 100644 app/Main.hs create mode 100644 elegant-git.cabal create mode 100644 elegant-git.iml create mode 100644 hie.yaml create mode 100644 package.yaml create mode 100644 src/Elegit/Cli/Action/ShowWork.hs create mode 100644 src/Elegit/Cli/Command.hs create mode 100644 src/Elegit/Cli/Parser.hs create mode 100644 src/Elegit/Git/Action.hs create mode 100644 src/Elegit/Git/Runner/Real.hs create mode 100644 src/Elegit/Git/Runner/Simulated.hs create mode 100644 src/Lib.hs create mode 100644 stack.yaml create mode 100644 stack.yaml.lock create mode 100644 test/Elegit/Cli/Action/ShowWorkSpec.hs create mode 100644 test/Elegit/Git/Runner/SimulatedSpec.hs create mode 100644 test/Spec.hs diff --git a/.github/workflows/haskell-quality-pipeline.yml b/.github/workflows/haskell-quality-pipeline.yml new file mode 100644 index 0000000..4b18816 --- /dev/null +++ b/.github/workflows/haskell-quality-pipeline.yml @@ -0,0 +1,53 @@ +name: Haskell Test CI + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +permissions: + contents: read + +env: + GHC_VERSION: 9.2.4 + STACK_VERSION: 2.7.3 + HLINT_VERSION: 3.3.6 + +jobs: + tests: + strategy: + matrix: + # os: [ubuntu-latest, macos-latest] + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} + name: "Quality pipeline" + + steps: + - uses: actions/checkout@v3 + - uses: actions/cache@v3 + name: Cache .stack + with: + path: ~/.stack + key: ${{ runner.os }}-stack-global-${{ hashFiles('stack.yaml') }}-${{ hashFiles('package.yaml') }}-${{ hashFiles('**/*.hs') }} + restore-keys: | + ${{ runner.os }}-stack-global- + - uses: haskell/actions/setup@v2 + with: + ghc-version: '${{ env.GHC_VERSION }}' + enable-stack: true + stack-version: '${{ env.STACK_VERSION }}' + - name: Install hlint + if: ${{ matrix.os == 'ubuntu-latest' }} + run: | + wget -O /tmp/hlint-${{ env.HLINT_VERSION }}.tar.gz https://github.com/ndmitchell/hlint/releases/download/v${{ env.HLINT_VERSION }}/hlint-${{ env.HLINT_VERSION }}-x86_64-linux.tar.gz + tar -xf /tmp/hlint-${{ env.HLINT_VERSION }}.tar.gz -C /tmp + cp /tmp/hlint-${{ env.HLINT_VERSION }}/hlint ~/.ghcup/bin/hlint + - name: Run hlint + if: ${{ matrix.os == 'ubuntu-latest' }} + run: hlint . + - name: Build dependencies + run: stack build --no-haddock --only-dependencies --fast --pedantic + - name: Run tests + run: stack test --fast --pedantic + diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 4b5ea9e..0000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,9 +0,0 @@ -name: Quality pipeline -on: [push] -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v1 - - name: Run development pipeline - run: ./workflows ci diff --git a/.gitignore b/.gitignore index 3452dd2..8fc5192 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ tmp/ site/ version +.stack-work/ +dist-newstyle/ +out/ diff --git a/.hlint.yaml b/.hlint.yaml new file mode 100644 index 0000000..8682bcf --- /dev/null +++ b/.hlint.yaml @@ -0,0 +1,1036 @@ +############################################################################ +## Universum +############################################################################ + +# There's no 'head' in Universum +- ignore: {name: "Use head"} + +# We have 'whenJust' for this +- ignore: {name: "Use Foldable.forM_"} + +- warn: {lhs: Data.Text.pack, rhs: Universum.toText} +- warn: {lhs: Data.Text.unpack, rhs: Universum.toString} + +- warn: {lhs: Data.Text.Lazy.pack, rhs: Universum.toLText} +- warn: {lhs: Data.Text.Lazy.unpack, rhs: Universum.toString} +- warn: {lhs: Data.Text.Lazy.toStrict, rhs: Universum.toText} +- warn: {lhs: Data.Text.Lazy.fromStrict, rhs: Universum.toLText} + +- warn: {lhs: Data.Text.pack (show x), rhs: Universum.show x} +- warn: {lhs: Data.Text.Lazy.pack (show x), rhs: Universum.show x} + +- warn: {lhs: Control.Exception.evaluate, rhs: evaluateWHNF} +- warn: {lhs: Control.Exception.evaluate (force x), rhs: evaluateNF x} +- warn: {lhs: Control.Exception.evaluate (x `deepseq` ()), rhs: evaluateNF_ x} +- warn: {lhs: void (evaluateWHNF x), rhs: evaluateWHNF_ x} +- warn: {lhs: void (evaluateNF x), rhs: evaluateNF_ x} + +## Containers +- hint: {lhs: Data.HashMap.Lazy.keys, rhs: Universum.keys} +- hint: {lhs: Data.HashMap.Strict.keys, rhs: Universum.keys} +- hint: {lhs: Data.Map.Lazy.keys, rhs: Universum.keys} +- hint: {lhs: Data.Map.Strict.keys, rhs: Universum.keys} +- hint: {lhs: Data.IntMap.Lazy.keys, rhs: Universum.keys} +- hint: {lhs: Data.IntMap.Strict.keys, rhs: Universum.keys} + +- hint: {lhs: Data.HashMap.Lazy.elems, rhs: Universum.elems} +- hint: {lhs: Data.HashMap.Strict.elems, rhs: Universum.elems} +- hint: {lhs: Data.Map.Lazy.elems, rhs: Universum.elems} +- hint: {lhs: Data.Map.Strict.elems, rhs: Universum.elems} +- hint: {lhs: Data.IntMap.Lazy.elems, rhs: Universum.elems} +- hint: {lhs: Data.IntMap.Strict.elems, rhs: Universum.elems} + +- hint: {lhs: Data.HashMap.Lazy.toList, rhs: Universum.toPairs} +- hint: {lhs: Data.HashMap.Strict.toList, rhs: Universum.toPairs} +- hint: {lhs: Data.Map.Lazy.toList, rhs: Universum.toPairs} +- hint: {lhs: Data.Map.Lazy.assocs, rhs: Universum.toPairs} +- hint: {lhs: Data.Map.Strict.toList, rhs: Universum.toPairs} +- hint: {lhs: Data.Map.Strict.assocs, rhs: Universum.toPairs} +- hint: {lhs: Data.IntMap.Lazy.toList, rhs: Universum.toPairs} +- hint: {lhs: Data.IntMap.Lazy.assocs, rhs: Universum.toPairs} +- hint: {lhs: Data.IntMap.Strict.toList, rhs: Universum.toPairs} +- hint: {lhs: Data.IntMap.Strict.assocs, rhs: Universum.toPairs} + +- warn: { lhs: Data.Map.toAscList (Data.Map.fromList x) + , rhs: Universum.sortWith fst x + } +- warn: { lhs: Data.Map.toDescList (Data.Map.fromList x) + , rhs: Universum.sortWith (Down . fst) x + } + +- warn: {lhs: Data.Set.toList (Data.Set.fromList l), rhs: Universum.sortNub l} +- warn: {lhs: Data.Set.assocs (Data.Set.fromList l), rhs: Universum.sortNub l} +- warn: {lhs: Data.Set.toAscList (Data.Set.fromList l), rhs: Universum.sortNub l} + +- warn: {lhs: Data.HashSet.toList (Data.HashSet.fromList l), rhs: Universum.unstableNub} + +- hint: { lhs: nub, rhs: Universum.ordNub + , note: "'nub' is O(n^2), 'ordNub' is O(n log n)" } + +- warn: { lhs: sortBy (comparing f), rhs: Universum.sortWith f + , note: "If the function you are using for 'comparing' is slow, use 'sortOn' instead of 'sortWith', because 'sortOn' caches applications the function and 'sortWith' doesn't." } + +- warn: { lhs: sortOn fst, rhs: Universum.sortWith fst + , note: "'sortWith' will be faster here because it doesn't do caching" } +- warn: { lhs: sortOn snd, rhs: Universum.sortWith snd + , note: "'sortWith' will be faster here because it doesn't do caching" } +- warn: { lhs: sortOn (Down . fst), rhs: Universum.sortWith (Down . fst) + , note: "'sortWith' will be faster here because it doesn't do caching" } +- warn: { lhs: sortOn (Down . snd), rhs: Universum.sortWith (Down . snd) + , note: "'sortWith' will be faster here because it doesn't do caching" } + +- warn: {lhs: map fst &&& map snd, rhs: unzip} + +- warn: {lhs: f >>= guard, rhs: guardM} +- warn: {lhs: guard =<< f, rhs: guardM} + +- warn: {lhs: fmap concat (mapM f s), rhs: Universum.concatMapM f s} +- warn: {lhs: concat <$> mapM f s, rhs: Universum.concatMapM f s} + +- warn: {lhs: fmap concat (forM f s), rhs: Universum.concatForM s f} +- warn: {lhs: fmap concat (for f s), rhs: Universum.concatForM s f} +- warn: {lhs: concat <$> forM f s, rhs: Universum.concatForM s f} +- warn: {lhs: concat <$> for f s, rhs: Universum.concatForM s f} + +- hint: { lhs: fmap and (sequence s), rhs: Universum.andM s + , note: "Applying this hint would mean that some actions\n that were being executed previously would no longer be executed." } +- hint: { lhs: and <$> sequence s, rhs: Universum.andM s + , note: "Applying this hint would mean that some actions\n that were being executed previously would no longer be executed." } + +- hint: { lhs: fmap or (sequence s), rhs: Universum.orM s + , note: "Applying this hint would mean that some actions\n that were being executed previously would no longer be executed." } +- hint: { lhs: or <$> sequence s, rhs: Universum.orM s + , note: "Applying this hint would mean that some actions\n that were being executed previously would no longer be executed." } + +- hint: { lhs: fmap and (mapM f s), rhs: Universum.allM f s + , note: "Applying this hint would mean that some actions\n that were being executed previously would no longer be executed." } +- hint: { lhs: and <$> mapM f s, rhs: Universum.allM f s + , note: "Applying this hint would mean that some actions\n that were being executed previously would no longer be executed." } +- hint: { lhs: fmap or (mapM f s), rhs: Universum.anyM f s + , note: "Applying this hint would mean that some actions\n that were being executed previously would no longer be executed." } +- hint: { lhs: or <$> mapM f s, rhs: Universum.anyM f s + , note: "Applying this hint would mean that some actions\n that were being executed previously would no longer be executed." } + +- warn: {lhs: whenM (not <$> x), rhs: unlessM x} +- warn: {lhs: unlessM (not <$> x), rhs: whenM x} + +- warn: {lhs: either (const True) (const False), rhs: isLeft} +- warn: {lhs: either (const False) (const True), rhs: isRight} + +- warn: {lhs: either id (const a), rhs: fromLeft a} +- warn: {lhs: either (const b) id, rhs: fromRight b} + +- warn: {lhs: either Just (const Nothing), rhs: leftToMaybe} +- warn: {lhs: either (const Nothing) Just, rhs: rightToMaybe} +- warn: {lhs: maybe (Left l) Right, rhs: maybeToRight} +- warn: {lhs: maybe (Right r) Left, rhs: maybeToLeft} + +- warn: {lhs: fromMaybe mempty, rhs: maybeToMonoid} +- warn: {lhs: "m ?: mempty", rhs: maybeToMonoid m} + + +- hint: {lhs: pure (), rhs: pass} +- hint: {lhs: return (), rhs: pass} + +# Probably will be reduced when function equality is done: +# https://github.com/ndmitchell/hlint/issues/434 +- warn: {lhs: (case m of Just x -> f x; Nothing -> pure () ), rhs: Universum.whenJust m f} +- warn: {lhs: (case m of Just x -> f x; Nothing -> return ()), rhs: Universum.whenJust m f} +- warn: {lhs: (case m of Just x -> f x; Nothing -> pass ), rhs: Universum.whenJust m f} +- warn: {lhs: (case m of Nothing -> pure () ; Just x -> f x), rhs: Universum.whenJust m f} +- warn: {lhs: (case m of Nothing -> return (); Just x -> f x), rhs: Universum.whenJust m f} +- warn: {lhs: (case m of Nothing -> pass ; Just x -> f x), rhs: Universum.whenJust m f} +- warn: {lhs: (maybe (pure ()) f m), rhs: Universum.whenJust m f} +- warn: {lhs: (maybe (return ()) f m), rhs: Universum.whenJust m f} +- warn: {lhs: (maybe pass f m), rhs: Universum.whenJust m f} + +- warn: {lhs: (m >>= \case Just x -> f x; Nothing -> pure () ), rhs: Universum.whenJustM m f} +- warn: {lhs: (m >>= \case Just x -> f x; Nothing -> return ()), rhs: Universum.whenJustM m f} +- warn: {lhs: (m >>= \case Just x -> f x; Nothing -> pass ), rhs: Universum.whenJustM m f} +- warn: {lhs: (m >>= \case Nothing -> pure () ; Just x -> f x), rhs: Universum.whenJustM m f} +- warn: {lhs: (m >>= \case Nothing -> return (); Just x -> f x), rhs: Universum.whenJustM m f} +- warn: {lhs: (m >>= \case Nothing -> pass ; Just x -> f x), rhs: Universum.whenJustM m f} +- warn: {lhs: (maybe (pure ()) f =<< m), rhs: Universum.whenJustM m f} +- warn: {lhs: (maybe (return ()) f =<< m), rhs: Universum.whenJustM m f} +- warn: {lhs: (maybe pass f =<< m), rhs: Universum.whenJustM m f} +- warn: {lhs: (m >>= maybe (pure ()) f), rhs: Universum.whenJustM m f} +- warn: {lhs: (m >>= maybe (return ()) f), rhs: Universum.whenJustM m f} +- warn: {lhs: (m >>= maybe pass f), rhs: Universum.whenJustM m f} + +- warn: {lhs: (case m of Just _ -> pure () ; Nothing -> x), rhs: Universum.whenNothing_ m x} +- warn: {lhs: (case m of Just _ -> return (); Nothing -> x), rhs: Universum.whenNothing_ m x} +- warn: {lhs: (case m of Just _ -> pass ; Nothing -> x), rhs: Universum.whenNothing_ m x} +- warn: {lhs: (case m of Nothing -> x; Just _ -> pure () ), rhs: Universum.whenNothing_ m x} +- warn: {lhs: (case m of Nothing -> x; Just _ -> return ()), rhs: Universum.whenNothing_ m x} +- warn: {lhs: (case m of Nothing -> x; Just _ -> pass ), rhs: Universum.whenNothing_ m x} +- warn: {lhs: (maybe x (\_ -> pure () ) m), rhs: Universum.whenNothing_ m x} +- warn: {lhs: (maybe x (\_ -> return () ) m), rhs: Universum.whenNothing_ m x} +- warn: {lhs: (maybe x (\_ -> pass ) m), rhs: Universum.whenNothing_ m x} +- warn: {lhs: (maybe x (const (pure () )) m), rhs: Universum.whenNothing_ m x} +- warn: {lhs: (maybe x (const (return ())) m), rhs: Universum.whenNothing_ m x} +- warn: {lhs: (maybe x (const (pass )) m), rhs: Universum.whenNothing_ m x} + +- warn: {lhs: (m >>= \case Just _ -> pure () ; Nothing -> x), rhs: Universum.whenNothingM_ m x} +- warn: {lhs: (m >>= \case Just _ -> return (); Nothing -> x), rhs: Universum.whenNothingM_ m x} +- warn: {lhs: (m >>= \case Just _ -> pass ; Nothing -> x), rhs: Universum.whenNothingM_ m x} +- warn: {lhs: (m >>= \case Nothing -> x; Just _ -> pure () ), rhs: Universum.whenNothingM_ m x} +- warn: {lhs: (m >>= \case Nothing -> x; Just _ -> return ()), rhs: Universum.whenNothingM_ m x} +- warn: {lhs: (m >>= \case Nothing -> x; Just _ -> pass ), rhs: Universum.whenNothingM_ m x} +- warn: {lhs: (maybe x (\_ -> pure () ) =<< m), rhs: Universum.whenNothingM_ m x} +- warn: {lhs: (maybe x (\_ -> return () ) =<< m), rhs: Universum.whenNothingM_ m x} +- warn: {lhs: (maybe x (\_ -> pass ) =<< m), rhs: Universum.whenNothingM_ m x} +- warn: {lhs: (maybe x (const (pure () )) =<< m), rhs: Universum.whenNothingM_ m x} +- warn: {lhs: (maybe x (const (return ())) =<< m), rhs: Universum.whenNothingM_ m x} +- warn: {lhs: (maybe x (const (pass )) =<< m), rhs: Universum.whenNothingM_ m x} +- warn: {lhs: (m >>= maybe x (\_ -> pure ()) ), rhs: Universum.whenNothingM_ m x} +- warn: {lhs: (m >>= maybe x (\_ -> return ()) ), rhs: Universum.whenNothingM_ m x} +- warn: {lhs: (m >>= maybe x (\_ -> pass) ), rhs: Universum.whenNothingM_ m x} +- warn: {lhs: (m >>= maybe x (const (pure ()) )), rhs: Universum.whenNothingM_ m x} +- warn: {lhs: (m >>= maybe x (const (return ()))), rhs: Universum.whenNothingM_ m x} +- warn: {lhs: (m >>= maybe x (const (pass) )), rhs: Universum.whenNothingM_ m x} + +- warn: {lhs: (case m of Left x -> f x; Right _ -> pure () ), rhs: Universum.whenLeft m f} +- warn: {lhs: (case m of Left x -> f x; Right _ -> return ()), rhs: Universum.whenLeft m f} +- warn: {lhs: (case m of Left x -> f x; Right _ -> pass ), rhs: Universum.whenLeft m f} +- warn: {lhs: (case m of Right _ -> pure () ; Left x -> f x), rhs: Universum.whenLeft m f} +- warn: {lhs: (case m of Right _ -> return (); Left x -> f x), rhs: Universum.whenLeft m f} +- warn: {lhs: (case m of Right _ -> pass ; Left x -> f x), rhs: Universum.whenLeft m f} +- warn: {lhs: (either f (\_ -> pure () ) m), rhs: Universum.whenLeft m f} +- warn: {lhs: (either f (\_ -> return () ) m), rhs: Universum.whenLeft m f} +- warn: {lhs: (either f (\_ -> pass ) m), rhs: Universum.whenLeft m f} +- warn: {lhs: (either f (const (pure () )) m), rhs: Universum.whenLeft m f} +- warn: {lhs: (either f (const (return ())) m), rhs: Universum.whenLeft m f} +- warn: {lhs: (either f (const (pass )) m), rhs: Universum.whenLeft m f} + +- warn: {lhs: (m >>= \case Left x -> f x; Right _ -> pure () ), rhs: Universum.whenLeftM m f} +- warn: {lhs: (m >>= \case Left x -> f x; Right _ -> return ()), rhs: Universum.whenLeftM m f} +- warn: {lhs: (m >>= \case Left x -> f x; Right _ -> pass ), rhs: Universum.whenLeftM m f} +- warn: {lhs: (m >>= \case Right _ -> pure () ; Left x -> f x), rhs: Universum.whenLeftM m f} +- warn: {lhs: (m >>= \case Right _ -> return (); Left x -> f x), rhs: Universum.whenLeftM m f} +- warn: {lhs: (m >>= \case Right _ -> pass ; Left x -> f x), rhs: Universum.whenLeftM m f} +- warn: {lhs: (either f (\_ -> pure () ) =<< m), rhs: Universum.whenLeftM m f} +- warn: {lhs: (either f (\_ -> return () ) =<< m), rhs: Universum.whenLeftM m f} +- warn: {lhs: (either f (\_ -> pass ) =<< m), rhs: Universum.whenLeftM m f} +- warn: {lhs: (either f (const (pure () )) =<< m), rhs: Universum.whenLeftM m f} +- warn: {lhs: (either f (const (return ())) =<< m), rhs: Universum.whenLeftM m f} +- warn: {lhs: (either f (const (pass )) =<< m), rhs: Universum.whenLeftM m f} +- warn: {lhs: (m >>= either f (\_ -> pure ()) ), rhs: Universum.whenLeftM m f} +- warn: {lhs: (m >>= either f (\_ -> return ()) ), rhs: Universum.whenLeftM m f} +- warn: {lhs: (m >>= either f (\_ -> pass) ), rhs: Universum.whenLeftM m f} +- warn: {lhs: (m >>= either f (const (pure ()) )), rhs: Universum.whenLeftM m f} +- warn: {lhs: (m >>= either f (const (return ()))), rhs: Universum.whenLeftM m f} +- warn: {lhs: (m >>= either f (const (pass) )), rhs: Universum.whenLeftM m f} + +- warn: {lhs: (case m of Right x -> f x; Left _ -> pure () ), rhs: Universum.whenRight m f} +- warn: {lhs: (case m of Right x -> f x; Left _ -> return ()), rhs: Universum.whenRight m f} +- warn: {lhs: (case m of Right x -> f x; Left _ -> pass ), rhs: Universum.whenRight m f} +- warn: {lhs: (case m of Left _ -> pure () ; Right x -> f x), rhs: Universum.whenRight m f} +- warn: {lhs: (case m of Left _ -> return (); Right x -> f x), rhs: Universum.whenRight m f} +- warn: {lhs: (case m of Left _ -> pass ; Right x -> f x), rhs: Universum.whenRight m f} +- warn: {lhs: (either (\_ -> pure () ) f m), rhs: Universum.whenRight m f} +- warn: {lhs: (either (\_ -> return () ) f m), rhs: Universum.whenRight m f} +- warn: {lhs: (either (\_ -> pass ) f m), rhs: Universum.whenRight m f} +- warn: {lhs: (either (const (pure () )) f m), rhs: Universum.whenRight m f} +- warn: {lhs: (either (const (return ())) f m), rhs: Universum.whenRight m f} +- warn: {lhs: (either (const (pass )) f m), rhs: Universum.whenRight m f} + +- warn: {lhs: (m >>= \case Right x -> f x; Left _ -> pure () ), rhs: Universum.whenRightM m f} +- warn: {lhs: (m >>= \case Right x -> f x; Left _ -> return ()), rhs: Universum.whenRightM m f} +- warn: {lhs: (m >>= \case Right x -> f x; Left _ -> pass ), rhs: Universum.whenRightM m f} +- warn: {lhs: (m >>= \case Left _ -> pure () ; Right x -> f x), rhs: Universum.whenRightM m f} +- warn: {lhs: (m >>= \case Left _ -> return (); Right x -> f x), rhs: Universum.whenRightM m f} +- warn: {lhs: (m >>= \case Left _ -> pass ; Right x -> f x), rhs: Universum.whenRightM m f} +- warn: {lhs: (either (\_ -> pure () ) f =<< m), rhs: Universum.whenRightM m f} +- warn: {lhs: (either (\_ -> return () ) f =<< m), rhs: Universum.whenRightM m f} +- warn: {lhs: (either (\_ -> pass ) f =<< m), rhs: Universum.whenRightM m f} +- warn: {lhs: (either (const (pure () )) f =<< m), rhs: Universum.whenRightM m f} +- warn: {lhs: (either (const (return ())) f =<< m), rhs: Universum.whenRightM m f} +- warn: {lhs: (either (const (pass )) f =<< m), rhs: Universum.whenRightM m f} +- warn: {lhs: (m >>= either (\_ -> pure ()) f), rhs: Universum.whenRightM m f} +- warn: {lhs: (m >>= either (\_ -> return ()) f), rhs: Universum.whenRightM m f} +- warn: {lhs: (m >>= either (\_ -> pass) f), rhs: Universum.whenRightM m f} +- warn: {lhs: (m >>= either (const (pure ()) ) f), rhs: Universum.whenRightM m f} +- warn: {lhs: (m >>= either (const (return ())) f), rhs: Universum.whenRightM m f} +- warn: {lhs: (m >>= either (const (pass) ) f), rhs: Universum.whenRightM m f} + +- warn: {lhs: "(case m of [] -> return (); (x:xs) -> f (x :| xs))", rhs: Universum.whenNotNull m f} +- warn: {lhs: "(case m of [] -> pure () ; (x:xs) -> f (x :| xs))", rhs: Universum.whenNotNull m f} +- warn: {lhs: "(case m of [] -> pass ; (x:xs) -> f (x :| xs))", rhs: Universum.whenNotNull m f} +- warn: {lhs: "(case m of (x:xs) -> f (x :| xs); [] -> return ())", rhs: Universum.whenNotNull m f} +- warn: {lhs: "(case m of (x:xs) -> f (x :| xs); [] -> pure () )", rhs: Universum.whenNotNull m f} +- warn: {lhs: "(case m of (x:xs) -> f (x :| xs); [] -> pass )", rhs: Universum.whenNotNull m f} +- warn: {lhs: "(m >>= \\case [] -> pass ; (x:xs) -> f (x :| xs))", rhs: Universum.whenNotNullM m f} +- warn: {lhs: "(m >>= \\case [] -> pure () ; (x:xs) -> f (x :| xs))", rhs: Universum.whenNotNullM m f} +- warn: {lhs: "(m >>= \\case [] -> return (); (x:xs) -> f (x :| xs))", rhs: Universum.whenNotNullM m f} +- warn: {lhs: "(m >>= \\case (x:xs) -> f (x :| xs); [] -> pass )", rhs: Universum.whenNotNullM m f} +- warn: {lhs: "(m >>= \\case (x:xs) -> f (x :| xs); [] -> pure () )", rhs: Universum.whenNotNullM m f} +- warn: {lhs: "(m >>= \\case (x:xs) -> f (x :| xs); [] -> return ())", rhs: Universum.whenNotNullM m f} + +- warn: {lhs: mapMaybe leftToMaybe, rhs: lefts} +- warn: {lhs: mapMaybe rightToMaybe, rhs: rights} + +############################################################################ +## Reexports +############################################################################ + +## Applicative +- warn: { name: "Use 'Alternative' from Universum" + , lhs: Control.Applicative.Alternative, rhs: Universum.Alternative } +- warn: { name: "Use 'empty' from Universum" + , lhs: Control.Applicative.empty, rhs: Universum.empty } +- warn: { name: "Use '(<|>)' from Universum" + , lhs: Control.Applicative.(<|>), rhs: Universum.(<|>) } +- warn: { name: "Use 'some' from Universum" + , lhs: Control.Applicative.some, rhs: Universum.some } +- warn: { name: "Use 'many' from Universum" + , lhs: Control.Applicative.many, rhs: Universum.many } +- warn: { name: "Use 'Const' from Universum" + , lhs: Control.Applicative.Const, rhs: Universum.Const } +- warn: { name: "Use 'getConst' from Universum" + , lhs: Control.Applicative.getConst, rhs: Universum.getConst } +- warn: { name: "Use 'ZipList' from Universum" + , lhs: Control.Applicative.ZipList, rhs: Universum.ZipList } +- warn: { name: "Use 'getZipList' from Universum" + , lhs: Control.Applicative.getZipList, rhs: Universum.getZipList } +- warn: { name: "Use 'liftA2' from Universum" + , lhs: Control.Applicative.liftA2, rhs: Universum.liftA2 } +- warn: { name: "Use 'liftA3' from Universum" + , lhs: Control.Applicative.liftA3, rhs: Universum.liftA3 } +- warn: { name: "Use 'optional' from Universum" + , lhs: Control.Applicative.optional, rhs: Universum.optional } +- warn: { name: "Use '(<**>)' from Universum" + , lhs: Control.Applicative.(<**>), rhs: Universum.(<**>) } + +## Base +- warn: { name: "Use 'xor' from Universum" + , lhs: Data.Bits.xor, rhs: Universum.xor } + +- warn: { name: "Use 'chr' from Universum" + , lhs: Data.Char.chr, rhs: Universum.chr } + +- warn: { name: "Use 'Int16' from Universum" + , lhs: Data.Int.Int16, rhs: Universum.Int16 } +- warn: { name: "Use 'Int32' from Universum" + , lhs: Data.Int.Int32, rhs: Universum.Int32 } +- warn: { name: "Use 'Int64' from Universum" + , lhs: Data.Int.Int64, rhs: Universum.Int64 } +- warn: { name: "Use 'Int8' from Universum" + , lhs: Data.Int.Int8, rhs: Universum.Int8 } + +- warn: { name: "Use 'Word16' from Universum" + , lhs: Data.Word.Word16, rhs: Universum.Word16 } +- warn: { name: "Use 'Word32' from Universum" + , lhs: Data.Word.Word32, rhs: Universum.Word32 } +- warn: { name: "Use 'Word64' from Universum" + , lhs: Data.Word.Word64, rhs: Universum.Word64 } +- warn: { name: "Use 'Word8' from Universum" + , lhs: Data.Word.Word8, rhs: Universum.Word8 } +- warn: { name: "Use 'byteSwap16' from Universum" + , lhs: Data.Word.byteSwap16, rhs: Universum.byteSwap16 } +- warn: { name: "Use 'byteSwap32' from Universum" + , lhs: Data.Word.byteSwap32, rhs: Universum.byteSwap32 } +- warn: { name: "Use 'byteSwap64' from Universum" + , lhs: Data.Word.byteSwap64, rhs: Universum.byteSwap64 } + +- warn: { name: "Use 'Natural' from Universum" + , lhs: Numeric.Natural.Natural, rhs: Universum.Natural } + +- warn: { name: "Use 'Handle' from Universum" + , lhs: System.IO.Handle, rhs: Universum.Handle } +- warn: { name: "Use 'IOMode' from Universum" + , lhs: System.IO.IOMode, rhs: Universum.IOMode } +- warn: { name: "Use 'ReadMode' from Universum" + , lhs: System.IO.ReadMode, rhs: Universum.ReadMode } +- warn: { name: "Use 'WriteMode' from Universum" + , lhs: System.IO.WriteMode, rhs: Universum.WriteMode } +- warn: { name: "Use 'AppendMode' from Universum" + , lhs: System.IO.AppendMode, rhs: Universum.AppendMode } +- warn: { name: "Use 'ReadWriteMode' from Universum" + , lhs: System.IO.ReadWriteMode, rhs: Universum.ReadWriteMode } +- warn: { name: "Use 'stderr' from Universum" + , lhs: System.IO.stderr, rhs: Universum.stderr } +- warn: { name: "Use 'stdin' from Universum" + , lhs: System.IO.stdin, rhs: Universum.stdin } +- warn: { name: "Use 'stdout' from Universum" + , lhs: System.IO.stdout, rhs: Universum.stdout } +- warn: { name: "Use 'withFile' from Universum" + , lhs: System.IO.withFile, rhs: Universum.withFile } + +- warn: { name: "Use 'foldlM' from Universum" + , lhs: Data.Foldable.foldlM, rhs: Universum.foldlM } +- warn: { name: "Use 'foldrM' from Universum" + , lhs: Data.Foldable.foldrM, rhs: Universum.foldrM } +- warn: { name: "Use 'maximumBy' from Universum" + , lhs: Data.Foldable.maximumBy, rhs: Universum.maximumBy } +- warn: { name: "Use 'minimumBy' from Universum" + , lhs: Data.Foldable.minimumBy, rhs: Universum.minimumBy } + +- warn: { name: "Use 'Down' from Universum" + , lhs: Data.Ord.Down, rhs: Universum.Down } +- warn: { name: "Use 'comparing' from Universum" + , lhs: Data.Ord.comparing, rhs: Universum.comparing } + +- warn: { name: "Use 'fmapDefault' from Universum" + , lhs: Data.Traversable.fmapDefault, rhs: Universum.fmapDefault } +- warn: { name: "Use 'foldMapDefault' from Universum" + , lhs: Data.Traversable.foldMapDefault, rhs: Universum.foldMapDefault } +- warn: { name: "Use 'forM' from Universum" + , lhs: Data.Traversable.forM, rhs: Universum.forM } +- warn: { name: "Use 'mapAccumL' from Universum" + , lhs: Data.Traversable.mapAccumL, rhs: Universum.mapAccumL } +- warn: { name: "Use 'mapAccumR' from Universum" + , lhs: Data.Traversable.mapAccumR, rhs: Universum.mapAccumR } + +- warn: { name: "Use 'Proxy' from Universum" + , lhs: Data.Proxy.Proxy, rhs: Universum.Proxy } + +- warn: { name: "Use 'Typeable' from Universum" + , lhs: Data.Typeable.Typeable, rhs: Universum.Typeable } + +- warn: { name: "Use 'Void' from Universum" + , lhs: Data.Void.Void, rhs: Universum.Void } +- warn: { name: "Use 'absurd' from Universum" + , lhs: Data.Void.absurd, rhs: Universum.absurd } +- warn: { name: "Use 'vacuous' from Universum" + , lhs: Data.Void.vacuous, rhs: Universum.vacuous } + +- warn: { name: "Use 'maxInt' from Universum" + , lhs: Data.Base.maxInt, rhs: Universum.maxInt } +- warn: { name: "Use 'minInt' from Universum" + , lhs: Data.Base.minInt, rhs: Universum.minInt } +- warn: { name: "Use 'ord' from Universum" + , lhs: Data.Base.ord, rhs: Universum.ord } + +- warn: { name: "Use 'boundedEnumFrom' from Universum" + , lhs: GHC.Enum.boundedEnumFrom, rhs: Universum.boundedEnumFrom } +- warn: { name: "Use 'boundedEnumFromThen' from Universum" + , lhs: GHC.Enum.boundedEnumFromThen, rhs: Universum.boundedEnumFromThen } + +- warn: { name: "Use 'Constraint' from Universum" + , lhs: GHC.Exts.Constraint, rhs: Universum.Constraint } +- warn: { name: "Use 'FunPtr' from Universum" + , lhs: GHC.Exts.FunPtr, rhs: Universum.FunPtr } +- warn: { name: "Use 'Ptr' from Universum" + , lhs: GHC.Exts.Ptr, rhs: Universum.Ptr } + +- warn: { name: "Use 'Generic' from Universum" + , lhs: GHC.Generics.Generic, rhs: Universum.Generic } + +- warn: { name: "Use 'Ratio' from Universum" + , lhs: GHC.Real.Ratio, rhs: Universum.Ratio } +- warn: { name: "Use 'Rational' from Universum" + , lhs: GHC.Real.Rational, rhs: Universum.Rational } + +- warn: { name: "Use 'CmpNat' from Universum" + , lhs: GHC.TypeNats.CmpNat, rhs: Universum.CmpNat } +- warn: { name: "Use 'KnownNat' from Universum" + , lhs: GHC.TypeNats.KnownNat, rhs: Universum.KnownNat } +- warn: { name: "Use 'Nat' from Universum" + , lhs: GHC.TypeNats.Nat, rhs: Universum.Nat } +- warn: { name: "Use 'SomeNat' from Universum" + , lhs: GHC.TypeNats.SomeNat, rhs: Universum.SomeNat } +- warn: { name: "Use 'natVal' from Universum" + , lhs: GHC.TypeNats.natVal, rhs: Universum.natVal } +- warn: { name: "Use 'someNatVal' from Universum" + , lhs: GHC.TypeNats.someNatVal, rhs: Universum.someNatVal } + +- warn: { name: "Use 'CmpNat' from Universum" + , lhs: GHC.TypeLits.CmpNat, rhs: Universum.CmpNat } +- warn: { name: "Use 'KnownNat' from Universum" + , lhs: GHC.TypeLits.KnownNat, rhs: Universum.KnownNat } +- warn: { name: "Use 'Nat' from Universum" + , lhs: GHC.TypeLits.Nat, rhs: Universum.Nat } +- warn: { name: "Use 'SomeNat' from Universum" + , lhs: GHC.TypeLits.SomeNat, rhs: Universum.SomeNat } +- warn: { name: "Use 'natVal' from Universum" + , lhs: GHC.TypeLits.natVal, rhs: Universum.natVal } +- warn: { name: "Use 'someNatVal' from Universum" + , lhs: GHC.TypeLits.someNatVal, rhs: Universum.someNatVal } + +- warn: { name: "Use 'Coercible' from Universum" + , lhs: GHC.Types.Coercible, rhs: Universum.Coercible } + +- warn: { name: "Use 'getStackTrace' from Universum" + , lhs: GHC.ExecutionStack.getStackTrace, rhs: Universum.getStackTrace } +- warn: { name: "Use 'showStackTrace' from Universum" + , lhs: GHC.ExecutionStack.showStackTrace, rhs: Universum.showStackTrace } + +- warn: { name: "Use 'IsLabel' from Universum" + , lhs: GHC.OverloadedLabels.IsLabel, rhs: Universum.IsLabel } +- warn: { name: "Use 'fromLabel' from Universum" + , lhs: GHC.OverloadedLabels.fromLabel, rhs: Universum.fromLabel } + +- warn: { name: "Use 'CallStack' from Universum" + , lhs: GHC.Stack.CallStack, rhs: Universum.CallStack } +- warn: { name: "Use 'HasCallStack' from Universum" + , lhs: GHC.Stack.HasCallStack, rhs: Universum.HasCallStack } +- warn: { name: "Use 'callStack' from Universum" + , lhs: GHC.Stack.callStack, rhs: Universum.callStack } +- warn: { name: "Use 'currentCallStack' from Universum" + , lhs: GHC.Stack.currentCallStack, rhs: Universum.currentCallStack } +- warn: { name: "Use 'getCallStack' from Universum" + , lhs: GHC.Stack.getCallStack, rhs: Universum.getCallStack } +- warn: { name: "Use 'prettyCallStack' from Universum" + , lhs: GHC.Stack.prettyCallStack, rhs: Universum.prettyCallStack } +- warn: { name: "Use 'prettySrcLoc' from Universum" + , lhs: GHC.Stack.prettySrcLoc, rhs: Universum.prettySrcLoc } +- warn: { name: "Use 'withFrozenCallStack' from Universum" + , lhs: GHC.Stack.withFrozenCallStack, rhs: Universum.withFrozenCallStack } + +- warn: { name: "Use 'Type' from Universum" + , lhs: Data.Kind.Type, rhs: Universum.Type } + +## Bool + +- warn: { name: "Use 'guard' from Universum" + , lhs: Control.Monad.guard, rhs: Universum.guard } +- warn: { name: "Use 'unless' from Universum" + , lhs: Control.Monad.unless, rhs: Universum.unless } +- warn: { name: "Use 'when' from Universum" + , lhs: Control.Monad.when, rhs: Universum.when } +- warn: { name: "Use 'bool' from Universum" + , lhs: Data.Bool.bool, rhs: Universum.bool } + +## Container +- warn: { name: "Use 'Hashable' from Universum" + , lhs: Data.Hashable.Hashable, rhs: Universum.Hashable } +- warn: { name: "Use 'hashWithSalt' from Universum" + , lhs: Data.Hashable.hashWithSalt, rhs: Universum.hashWithSalt } +- warn: { name: "Use 'HashMap' from Universum" + , lhs: Data.HashMap.Strict.HashMap, rhs: Universum.HashMap } +- warn: { name: "Use 'HashSet' from Universum" + , lhs: Data.HashSet.HashSet, rhs: Universum.HashSet } +- warn: { name: "Use 'IntMap' from Universum" + , lhs: Data.IntMap.Strict.IntMap, rhs: Universum.IntMap } +- warn: { name: "Use 'IntSet' from Universum" + , lhs: Data.IntSet.IntSet, rhs: Universum.IntSet } +- warn: { name: "Use 'Map' from Universum" + , lhs: Data.Map.Strict.Map, rhs: Universum.Map } +- warn: { name: "Use 'Sequence' from Universum" + , lhs: Data.Sequence.Sequence, rhs: Universum.Sequence } +- warn: { name: "Use 'Set' from Universum" + , lhs: Data.Set.Set, rhs: Universum.Set } +- warn: { name: "Use 'swap' from Universum" + , lhs: Data.Tuple.swap, rhs: Universum.swap } +- warn: { name: "Use 'Vector' from Universum" + , lhs: Data.Vector.Vector, rhs: Universum.Vector } + +## Deepseq +- warn: { name: "Use 'NFData' from Universum" + , lhs: Control.DeepSeq.NFData, rhs: Universum.NFData } +- warn: { name: "Use 'rnf' from Universum" + , lhs: Control.DeepSeq.rnf, rhs: Universum.rnf } +- warn: { name: "Use 'deepseq' from Universum" + , lhs: Control.DeepSeq.deepseq, rhs: Universum.deepseq } +- warn: { name: "Use 'force' from Universum" + , lhs: Control.DeepSeq.force, rhs: Universum.force } +- warn: { name: "Use '($!!)' from Universum" + , lhs: "Control.DeepSeq.($!!)", rhs: "Universum.($!!)" } + +## Exception +- warn: { name: "Use 'Exception' from Universum" + , lhs: Control.Exception.Exception, rhs: Universum.Exception } +- warn: { name: "Use 'toException' from Universum" + , lhs: Control.Exception.toException, rhs: Universum.toException } +- warn: { name: "Use 'fromException' from Universum" + , lhs: Control.Exception.fromException, rhs: Universum.fromException } + +- warn: { name: "Use 'Exception' from Universum" + , lhs: Control.Exception.Safe.Exception, rhs: Universum.Exception } +- warn: { name: "Use 'toException' from Universum" + , lhs: Control.Exception.Safe.toException, rhs: Universum.toException } +- warn: { name: "Use 'fromException' from Universum" + , lhs: Control.Exception.Safe.fromException, rhs: Universum.fromException } +- warn: { name: "Use 'displayException' from Universum" + , lhs: Control.Exception.Safe.displayException, rhs: Universum.displayException } +- warn: { name: "Use 'MonadCatch' from Universum" + , lhs: Control.Exception.Safe.MonadCatch, rhs: Universum.MonadCatch } +- warn: { name: "Use 'MonadMask' from Universum" + , lhs: Control.Exception.Safe.MonadMask, rhs: Universum.MonadMask } +- warn: { name: "Use 'mask' from Universum" + , lhs: Control.Exception.Safe.mask, rhs: Universum.mask } +- warn: { name: "Use 'uninterruptibleMask' from Universum" + , lhs: Control.Exception.Safe.uninterruptibleMask, rhs: Universum.uninterruptibleMask } +- warn: { name: "Use 'MonadThrow' from Universum" + , lhs: Control.Exception.Safe.MonadThrow, rhs: Universum.MonadThrow } +- warn: { name: "Use 'SomeException' from Universum" + , lhs: Control.Exception.Safe.SomeException, rhs: Universum.SomeException } +- warn: { name: "Use 'bracket' from Universum" + , lhs: Control.Exception.Safe.bracket, rhs: Universum.bracket } +- warn: { name: "Use 'bracketOnError' from Universum" + , lhs: Control.Exception.Safe.bracketOnError, rhs: Universum.bracketOnError } +- warn: { name: "Use 'bracket_' from Universum" + , lhs: Control.Exception.Safe.bracket_, rhs: Universum.bracket_ } +- warn: { name: "Use 'catch' from Universum" + , lhs: Control.Exception.Safe.catch, rhs: Universum.catch } +- warn: { name: "Use 'catchAny' from Universum" + , lhs: Control.Exception.Safe.catchAny, rhs: Universum.catchAny } +- warn: { name: "Use 'finally' from Universum" + , lhs: Control.Exception.Safe.finally, rhs: Universum.finally } +- warn: { name: "Use 'handleAny' from Universum" + , lhs: Control.Exception.Safe.handleAny, rhs: Universum.handleAny } +- warn: { name: "Use 'onException' from Universum" + , lhs: Control.Exception.Safe.onException, rhs: Universum.onException } +- warn: { name: "Use 'throwM' from Universum" + , lhs: Control.Exception.Safe.throwM, rhs: Universum.throwM } +- warn: { name: "Use 'try' from Universum" + , lhs: Control.Exception.Safe.try, rhs: Universum.try } +- warn: { name: "Use 'tryAny' from Universum" + , lhs: Control.Exception.Safe.tryAny, rhs: Universum.tryAny } + +## Function +- warn: { name: "Use 'fix' from Universum" + , lhs: Data.Function.fix, rhs: Universum.fix } +- warn: { name: "Use 'on' from Universum" + , lhs: Data.Function.on, rhs: Universum.on } + +## Functor +- warn: { name: "Use '(&&&)' from Universum" + , lhs: Control.Arrow.(&&&), rhs: Universum.(&&&) } +- warn: { name: "Use 'Bifunctor' from Universum" + , lhs: Data.Bifunctor.Bifunctor, rhs: Universum.Bifunctor } +- warn: { name: "Use 'bimap' from Universum" + , lhs: Data.Bifunctor.bimap, rhs: Universum.bimap } +- warn: { name: "Use 'first' from Universum" + , lhs: Data.Bifunctor.first, rhs: Universum.first } +- warn: { name: "Use 'second' from Universum" + , lhs: Data.Bifunctor.second, rhs: Universum.second } +- warn: { name: "Use 'void' from Universum" + , lhs: Data.Functor.void, rhs: Universum.void } +- warn: { name: "Use '($>)' from Universum" + , lhs: Data.Functor.($>), rhs: Universum.($>) } +- warn: { name: "Use 'Compose' from Universum" + , lhs: Data.Functor.Compose.Compose, rhs: Universum.Compose } +- warn: { name: "Use 'getCompose' from Universum" + , lhs: Data.Functor.Compose.getCompose, rhs: Universum.getCompose } +- warn: { name: "Use 'Identity' from Universum" + , lhs: Data.Functor.Identity.Identity, rhs: Universum.Identity } +- warn: { name: "Use 'runIdentity' from Universum" + , lhs: Data.Functor.Identity.runIdentity, rhs: Universum.runIdentity } + +## List +- warn: { name: "Use 'genericDrop' from Universum" + , lhs: Data.List.genericDrop, rhs: Universum.genericDrop } +- warn: { name: "Use 'genericLength' from Universum" + , lhs: Data.List.genericLength, rhs: Universum.genericLength } +- warn: { name: "Use 'genericReplicate' from Universum" + , lhs: Data.List.genericReplicate, rhs: Universum.genericReplicate } +- warn: { name: "Use 'genericSplitAt' from Universum" + , lhs: Data.List.genericSplitAt, rhs: Universum.genericSplitAt } +- warn: { name: "Use 'genericTake' from Universum" + , lhs: Data.List.genericTake, rhs: Universum.genericTake } +- warn: { name: "Use 'group' from Universum" + , lhs: Data.List.group, rhs: Universum.group } +- warn: { name: "Use 'inits' from Universum" + , lhs: Data.List.inits, rhs: Universum.inits } +- warn: { name: "Use 'intercalate' from Universum" + , lhs: Data.List.intercalate, rhs: Universum.intercalate } +- warn: { name: "Use 'intersperse' from Universum" + , lhs: Data.List.intersperse, rhs: Universum.intersperse } +- warn: { name: "Use 'isPrefixOf' from Universum" + , lhs: Data.List.isPrefixOf, rhs: Universum.isPrefixOf } +- warn: { name: "Use 'permutations' from Universum" + , lhs: Data.List.permutations, rhs: Universum.permutations } +- warn: { name: "Use 'sort' from Universum" + , lhs: Data.List.sort, rhs: Universum.sort } +- warn: { name: "Use 'sortBy' from Universum" + , lhs: Data.List.sortBy, rhs: Universum.sortBy } +- warn: { name: "Use 'sortOn' from Universum" + , lhs: Data.List.sortOn, rhs: Universum.sortOn } +- warn: { name: "Use 'subsequences' from Universum" + , lhs: Data.List.subsequences, rhs: Universum.subsequences } +- warn: { name: "Use 'tails' from Universum" + , lhs: Data.List.tails, rhs: Universum.tails } +- warn: { name: "Use 'transpose' from Universum" + , lhs: Data.List.transpose, rhs: Universum.transpose } +- warn: { name: "Use 'unfoldr' from Universum" + , lhs: Data.List.unfoldr, rhs: Universum.unfoldr } + +- warn: { name: "Use 'NonEmpty' from Universum" + , lhs: Data.List.NonEmpty.NonEmpty, rhs: Universum.NonEmpty } +- warn: { name: "Use '(:|)' from Universum" + , lhs: "Data.List.NonEmpty.(:|)", rhs: "Universum.(:|)"} +- warn: { name: "Use 'nonEmpty' from Universum" + , lhs: Data.List.NonEmpty.nonEmpty, rhs: Universum.nonEmpty} +- warn: { name: "Use 'head' from Universum" + , lhs: Data.List.NonEmpty.head, rhs: Universum.head } +- warn: { name: "Use 'init' from Universum" + , lhs: Data.List.NonEmpty.init, rhs: Universum.init } +- warn: { name: "Use 'last' from Universum" + , lhs: Data.List.NonEmpty.last, rhs: Universum.last } +- warn: { name: "Use 'tail' from Universum" + , lhs: Data.List.NonEmpty.tail, rhs: Universum.tail } +- warn: { name: "Use 'sortWith' from Universum" + , lhs: GHC.Exts.sortWith, rhs: Universum.sortWith } + +## Monad +- warn: { name: "Use '(>=>)' from Universum" + , lhs: Control.Monad.(>=>), rhs: Universum.(>=>) } +- warn: { name: "Use '(<=<)' from Universum" + , lhs: Control.Monad.(<=<), rhs: Universum.(<=<) } +- warn: { name: "Use 'forever' from Universum" + , lhs: Control.Monad.forever, rhs: Universum.forever } +- warn: { name: "Use 'join' from Universum" + , lhs: Control.Monad.join, rhs: Universum.join } +- warn: { name: "Use 'mfilter' from Universum" + , lhs: Control.Monad.mfilter, rhs: Universum.mfilter } +- warn: { name: "Use 'filterM' from Universum" + , lhs: Control.Monad.filterM, rhs: Universum.filterM } +- warn: { name: "Use 'mapAndUnzipM' from Universum" + , lhs: Control.Monad.mapAndUnzipM, rhs: Universum.mapAndUnzipM } +- warn: { name: "Use 'zipWithM' from Universum" + , lhs: Control.Monad.zipWithM, rhs: Universum.zipWithM } +- warn: { name: "Use 'zipWithM_' from Universum" + , lhs: Control.Monad.zipWithM_, rhs: Universum.zipWithM_ } +- warn: { name: "Use 'foldM' from Universum" + , lhs: Control.Monad.foldM, rhs: Universum.foldM } +- warn: { name: "Use 'foldM_' from Universum" + , lhs: Control.Monad.foldM_, rhs: Universum.foldM_ } +- warn: { name: "Use 'replicateM' from Universum" + , lhs: Control.Monad.replicateM, rhs: Universum.replicateM } +- warn: { name: "Use 'replicateM_' from Universum" + , lhs: Control.Monad.replicateM_, rhs: Universum.replicateM_ } +- warn: { name: "Use 'liftM2' from Universum" + , lhs: Control.Monad.liftM2, rhs: Universum.liftM2 } +- warn: { name: "Use 'liftM3' from Universum" + , lhs: Control.Monad.liftM3, rhs: Universum.liftM3 } +- warn: { name: "Use 'liftM4' from Universum" + , lhs: Control.Monad.liftM4, rhs: Universum.liftM4 } +- warn: { name: "Use 'liftM5' from Universum" + , lhs: Control.Monad.liftM5, rhs: Universum.liftM5 } +- warn: { name: "Use 'ap' from Universum" + , lhs: Control.Monad.ap, rhs: Universum.ap } +- warn: { name: "Use '(<$!>)' from Universum" + , lhs: Control.Monad.(<$!>), rhs: Universum.(<$!>) } + +- warn: { name: "Use 'ExceptT' from Universum" + , lhs: Control.Monad.Except.ExceptT, rhs: Universum.ExceptT } +- warn: { name: "Use 'runExceptT' from Universum" + , lhs: Control.Monad.Except.runExceptT, rhs: Universum.runExceptT } + +- warn: { name: "Use 'MonadReader' from Universum" + , lhs: Control.Monad.Reader.MonadReader, rhs: Universum.MonadReader } +- warn: { name: "Use 'Reader' from Universum" + , lhs: Control.Monad.Reader.Reader, rhs: Universum.Reader } +- warn: { name: "Use 'ReaderT' from Universum" + , lhs: Control.Monad.Reader.ReaderT, rhs: Universum.ReaderT } +- warn: { name: "Use 'runReaderT' from Universum" + , lhs: Control.Monad.Reader.runReaderT, rhs: Universum.runReaderT } +- warn: { name: "Use 'ask' from Universum" + , lhs: Control.Monad.Reader.ask, rhs: Universum.ask } +- warn: { name: "Use 'local' from Universum" + , lhs: Control.Monad.Reader.local, rhs: Universum.local } +- warn: { name: "Use 'reader' from Universum" + , lhs: Control.Monad.Reader.reader, rhs: Universum.reader } +- warn: { name: "Use 'runReader' from Universum" + , lhs: Control.Monad.Reader.runReader, rhs: Universum.runReader } + +- warn: { name: "Use 'MonadState' from Universum" + , lhs: Control.Monad.State.Strict.MonadState, rhs: Universum.MonadState } +- warn: { name: "Use 'State' from Universum" + , lhs: Control.Monad.State.Strict.State, rhs: Universum.State } +- warn: { name: "Use 'StateT' from Universum" + , lhs: Control.Monad.State.Strict.StateT, rhs: Universum.StateT } +- warn: { name: "Use 'runStateT' from Universum" + , lhs: Control.Monad.State.Strict.runStateT, rhs: Universum.runStateT } +- warn: { name: "Use 'evalState' from Universum" + , lhs: Control.Monad.State.Strict.evalState, rhs: Universum.evalState } +- warn: { name: "Use 'evalStateT' from Universum" + , lhs: Control.Monad.State.Strict.evalStateT, rhs: Universum.evalStateT } +- warn: { name: "Use 'execState' from Universum" + , lhs: Control.Monad.State.Strict.execState, rhs: Universum.execState } +- warn: { name: "Use 'execStateT' from Universum" + , lhs: Control.Monad.State.Strict.execStateT, rhs: Universum.execStateT } +- warn: { name: "Use 'get' from Universum" + , lhs: Control.Monad.State.Strict.get, rhs: Universum.get } +- warn: { name: "Use 'gets' from Universum" + , lhs: Control.Monad.State.Strict.gets, rhs: Universum.gets } +- warn: { name: "Use 'modify' from Universum" + , lhs: Control.Monad.State.Strict.modify, rhs: Universum.modify } +- warn: { name: "Use 'modify'' from Universum" + , lhs: "Control.Monad.State.Strict.modify'", rhs: "Universum.modify'" } +- warn: { name: "Use 'put' from Universum" + , lhs: Control.Monad.State.Strict.put, rhs: Universum.put } +- warn: { name: "Use 'runState' from Universum" + , lhs: Control.Monad.State.Strict.runState, rhs: Universum.runState } +- warn: { name: "Use 'state' from Universum" + , lhs: Control.Monad.State.Strict.state, rhs: Universum.state } +- warn: { name: "Use 'withState' from Universum" + , lhs: Control.Monad.State.Strict.withState, rhs: Universum.withState } + +- warn: { name: "Use 'MonadFail' from Universum" + , lhs: Control.Monad.Fail.MonadFail, rhs: Universum.MonadFail } + + +- warn: { name: "Use 'MonadIO' from Universum" + , lhs: Control.Monad.Trans.MonadIO, rhs: Universum.MonadIO } +- warn: { name: "Use 'MonadTrans' from Universum" + , lhs: Control.Monad.Trans.MonadTrans, rhs: Universum.MonadTrans } +- warn: { name: "Use 'lift' from Universum" + , lhs: Control.Monad.Trans.lift, rhs: Universum.lift } +- warn: { name: "Use 'liftIO' from Universum" + , lhs: Control.Monad.Trans.liftIO, rhs: Universum.liftIO } + +- warn: { name: "Use 'IdentityT' from Universum" + , lhs: Control.Monad.Trans.Identity.IdentityT, rhs: Universum.IdentityT } +- warn: { name: "Use 'runIdentityT' from Universum" + , lhs: Control.Monad.Trans.Identity.runIdentityT, rhs: Universum.runIdentityT } + +- warn: { name: "Use 'MaybeT' from Universum" + , lhs: Control.Monad.Trans.Maybe.MaybeT, rhs: Universum.MaybeT } +- warn: { name: "Use 'maybeToExceptT' from Universum" + , lhs: Control.Monad.Trans.Maybe.maybeToExceptT, rhs: Universum.maybeToExceptT } +- warn: { name: "Use 'exceptToMaybeT' from Universum" + , lhs: Control.Monad.Trans.Maybe.exceptToMaybeT, rhs: Universum.exceptToMaybeT } + +- warn: { name: "Use 'catMaybes' from Universum" + , lhs: Data.Maybe.catMaybes, rhs: Universum.catMaybes } +- warn: { name: "Use 'fromMaybe' from Universum" + , lhs: Data.Maybe.fromMaybe, rhs: Universum.fromMaybe } +- warn: { name: "Use 'isJust' from Universum" + , lhs: Data.Maybe.isJust, rhs: Universum.isJust } +- warn: { name: "Use 'isNothing' from Universum" + , lhs: Data.Maybe.isNothing, rhs: Universum.isNothing } +- warn: { name: "Use 'listToMaybe' from Universum" + , lhs: Data.Maybe.listToMaybe, rhs: Universum.listToMaybe } +- warn: { name: "Use 'mapMaybe' from Universum" + , lhs: Data.Maybe.mapMaybe, rhs: Universum.mapMaybe } +- warn: { name: "Use 'maybeToList' from Universum" + , lhs: Data.Maybe.maybeToList, rhs: Universum.maybeToList } + +- warn: { name: "Use 'isLeft' from Universum" + , lhs: Data.Either.isLeft, rhs: Universum.isLeft } +- warn: { name: "Use 'isRight' from Universum" + , lhs: Data.Either.isRight, rhs: Universum.isRight } +- warn: { name: "Use 'lefts' from Universum" + , lhs: Data.Either.lefts, rhs: Universum.lefts } +- warn: { name: "Use 'partitionEithers' from Universum" + , lhs: Data.Either.partitionEithers, rhs: Universum.partitionEithers } +- warn: { name: "Use 'rights' from Universum" + , lhs: Data.Either.rights, rhs: Universum.rights } + +- warn: { name: "Use 'newTVar' from Universum" + , lhs: Control.Concurrent.STM.TVar.newTVar, rhs: Universum.newTVar } +- warn: { name: "Use 'readTVar' from Universum" + , lhs: Control.Concurrent.STM.TVar.readTVar, rhs: Universum.readTVar } +- warn: { name: "Use 'writeTVar' from Universum" + , lhs: Control.Concurrent.STM.TVar.writeTVar, rhs: Universum.writeTVar } +- warn: { name: "Use 'modifyTVar'' from Universum" + , lhs: "Control.Concurrent.STM.TVar.modifyTVar'", rhs: "Universum.modifyTVar'" } +- warn: { name: "Use 'newTVarIO' from Universum" + , lhs: Control.Concurrent.STM.TVar.newTVarIO, rhs: Universum.newTVarIO } +- warn: { name: "Use 'readTVarIO' from Universum" + , lhs: Control.Concurrent.STM.TVar.readTVarIO, rhs: Universum.readTVarIO } + +- warn: { name: "Use 'newIORef' from Universum" + , lhs: Data.IORef.newIORef, rhs: Universum.newIORef } +- warn: { name: "Use 'readIORef' from Universum" + , lhs: Data.IORef.readIORef, rhs: Universum.readIORef } +- warn: { name: "Use 'writeIORef' from Universum" + , lhs: Data.IORef.writeIORef, rhs: Universum.writeIORef } +- warn: { name: "Use 'modifyIORef' from Universum" + , lhs: Data.IORef.modifyIORef, rhs: Universum.modifyIORef } +- warn: { name: "Use 'modifyIORef'' from Universum" + , lhs: "Data.IORef.modifyIORef'", rhs: "Universum.modifyIORef'" } +- warn: { name: "Use 'atomicModifyIORef' from Universum" + , lhs: Data.IORef.atomicModifyIORef, rhs: Universum.atomicModifyIORef } +- warn: { name: "Use 'atomicModifyIORef'' from Universum" + , lhs: "Data.IORef.atomicModifyIORef'", rhs: "Universum.atomicModifyIORef'" } +- warn: { name: "Use 'atomicWriteIORef' from Universum" + , lhs: Data.IORef.atomicWriteIORef, rhs: Universum.atomicWriteIORef } + +## Monoid +- warn: { name: "Use 'All' from Universum" + , lhs: Data.Monoid.All, rhs: Universum.All } +- warn: { name: "Use 'Alt' from Universum" + , lhs: Data.Monoid.Alt, rhs: Universum.Alt } +- warn: { name: "Use 'Any' from Universum" + , lhs: Data.Monoid.Any, rhs: Universum.Any } +- warn: { name: "Use 'Dual' from Universum" + , lhs: Data.Monoid.Dual, rhs: Universum.Dual } +- warn: { name: "Use 'Endo' from Universum" + , lhs: Data.Monoid.Endo, rhs: Universum.Endo } +- warn: { name: "Use 'First' from Universum" + , lhs: Data.Monoid.First, rhs: Universum.First } +- warn: { name: "Use 'Last' from Universum" + , lhs: Data.Monoid.Last, rhs: Universum.Last } +- warn: { name: "Use 'Product' from Universum" + , lhs: Data.Monoid.Product, rhs: Universum.Product } +- warn: { name: "Use 'Sum' from Universum" + , lhs: Data.Monoid.Sum, rhs: Universum.Sum } + +- warn: { name: "Use 'Option' from Universum" + , lhs: Data.Semigroup.Option, rhs: Universum.Option } +- warn: { name: "Use 'Semigroup' from Universum" + , lhs: Data.Semigroup.Semigroup, rhs: Universum.Semigroup } +- warn: { name: "Use 'sconcat' from Universum" + , lhs: Data.Semigroup.sconcat, rhs: Universum.sconcat } +- warn: { name: "Use 'stimes' from Universum" + , lhs: Data.Semigroup.stimes, rhs: Universum.stimes } +- warn: { name: "Use '(<>)' from Universum" + , lhs: Data.Semigroup.(<>), rhs: Universum.(<>) } +- warn: { name: "Use 'WrappedMonoid' from Universum" + , lhs: Data.Semigroup.WrappedMonoid, rhs: Universum.WrappedMonoid } +- warn: { name: "Use 'cycle1' from Universum" + , lhs: Data.Semigroup.cycle1, rhs: Universum.cycle1 } +- warn: { name: "Use 'mtimesDefault' from Universum" + , lhs: Data.Semigroup.mtimesDefault, rhs: Universum.mtimesDefault } +- warn: { name: "Use 'stimesIdempotent' from Universum" + , lhs: Data.Semigroup.stimesIdempotent, rhs: Universum.stimesIdempotent } +- warn: { name: "Use 'stimesIdempotentMonoid' from Universum" + , lhs: Data.Semigroup.stimesIdempotentMonoid, rhs: Universum.stimesIdempotentMonoid } +- warn: { name: "Use 'stimesMonoid' from Universum" + , lhs: Data.Semigroup.stimesMonoid, rhs: Universum.stimesMonoid } + +## String +- warn: { name: "Use 'ByteString' from Universum" + , lhs: Data.ByteString.ByteString, rhs: Universum.ByteString } +- warn: { name: "Use 'IsString' from Universum" + , lhs: Data.String.IsString, rhs: Universum.IsString } + +- warn: { name: "Use 'Text' from Universum" + , lhs: Data.Text.Text, rhs: Universum.Text } +- warn: { name: "Use 'lines' from Universum" + , lhs: Data.Text.lines, rhs: Universum.lines } +- warn: { name: "Use 'unlines' from Universum" + , lhs: Data.Text.unlines, rhs: Universum.unlines } +- warn: { name: "Use 'words' from Universum" + , lhs: Data.Text.words, rhs: Universum.words } +- warn: { name: "Use 'unwords' from Universum" + , lhs: Data.Text.unwords, rhs: Universum.unwords } + +- warn: { name: "Use 'LText' from Universum" + , lhs: Data.Text.Lazy.Text, rhs: Universum.LText } +- warn: { name: "Use 'LByteString' from Universum" + , lhs: Data.ByteString.Lazy.LByteString, rhs: Universum.LByteString } + +- warn: { name: "Use 'Buildable' from Universum" + , lhs: Data.Text.Buildable, rhs: Universum.Buildable } +- warn: { name: "Use 'decodeUtf8'' from Universum" + , lhs: "Data.Text.Encoding.decodeUtf8'", rhs: "Universum.decodeUtf8'" } +- warn: { name: "Use 'decodeUtf8With' from Universum" + , lhs: Data.Text.Encoding.decodeUtf8With, rhs: Universum.decodeUtf8With } + +- warn: { name: "Use 'OnDecodeError' from Universum" + , lhs: Data.Text.Encoding.Error.OnDecodeError, rhs: Universum.OnDecodeError } +- warn: { name: "Use 'OnDecodeError' from Universum" + , lhs: Data.Text.Encoding.Error.OnDecodeError, rhs: Universum.OnDecodeError } +- warn: { name: "Use 'OnError' from Universum" + , lhs: Data.Text.Encoding.Error.OnError, rhs: Universum.OnError } +- warn: { name: "Use 'UnicodeException' from Universum" + , lhs: Data.Text.Encoding.Error.UnicodeException, rhs: Universum.UnicodeException } +- warn: { name: "Use 'lenientDecode' from Universum" + , lhs: Data.Text.Encoding.Error.lenientDecode, rhs: Universum.lenientDecode } +- warn: { name: "Use 'strictDecode' from Universum" + , lhs: Data.Text.Encoding.Error.strictDecode, rhs: Universum.strictDecode } + +- warn: { name: "Use 'fromStrict' from Universum" + , lhs: Data.Text.Lazy.fromStrict, rhs: Universum.fromStrict } +- warn: { name: "Use 'toStrict' from Universum" + , lhs: Data.Text.Lazy.toStrict, rhs: Universum.toStrict } + +- warn: { name: "Use 'readMaybe' from Universum" + , lhs: Text.Read.readMaybe, rhs: Universum.readMaybe } + +- warn: { name: "Use 'getLine' from Universum" + , lhs: Data.Text.IO.getLine, rhs: Universum.getLine } +- warn: { name: "Use 'readFile' from Universum" + , lhs: Data.Text.IO.readFile, rhs: Universum.readFile } +- warn: { name: "Use 'writeFile' from Universum" + , lhs: Data.Text.IO.writeFile, rhs: Universum.writeFile } +- warn: { name: "Use 'appendFile' from Universum" + , lhs: Data.Text.IO.appendFile, rhs: Universum.appendFile } + +## Unsafe +- warn: { name: "Use 'head' from Universum.Unsafe" + , lhs: Data.List.head, rhs: Universum.Unsafe.head + , note: "Use 'import qualified Universum.Unsafe as Unsafe (head)'" } +- warn: { name: "Use 'tail' from Universum.Unsafe" + , lhs: Data.List.tail, rhs: Universum.Unsafe.tail + , note: "Use 'import qualified Universum.Unsafe as Unsafe (tail)'" } +- warn: { name: "Use 'init' from Universum.Unsafe" + , lhs: Data.List.init, rhs: Universum.Unsafe.init + , note: "Use 'import qualified Universum.Unsafe as Unsafe (init)'" } +- warn: { name: "Use 'last' from Universum.Unsafe" + , lhs: Data.List.last, rhs: Universum.Unsafe.last + , note: "Use 'import qualified Universum.Unsafe as Unsafe (last)'" } +- warn: { name: "Use '(!!)' from Universum.Unsafe" + , lhs: "Data.List.(!!)", rhs: "Universum.Unsafe.(!!)" + , note: "Use 'import qualified Universum.Unsafe as Unsafe ((!!))'" } +- warn: { name: "Use 'fromJust' from Universum.Unsafe" + , lhs: "Data.Maybe.fromJust", rhs: "Universum.Unsafe.fromJust" + , note: "Use 'import qualified Universum.Unsafe as Unsafe (fromJust)'" } + +############################################################################ +## Lifted functions in Universum +############################################################################ + +## concurrency + +- warn: { name: "liftIO is not needed", lhs: liftIO newEmptyMVar, rhs: Universum.newEmptyMVar + , note: "If you import 'newEmptyMVar' from Universum, it's already lifted" } +- warn: { name: "liftIO is not needed", lhs: liftIO (newMVar x), rhs: Universum.newMVar x + , note: "If you import 'newMVar' from Universum, it's already lifted" } +- warn: { name: "liftIO is not needed", lhs: liftIO (putMVar x y), rhs: Universum.putMVar x y + , note: "If you import 'putMVar' from Universum, it's already lifted" } +- warn: { name: "liftIO is not needed", lhs: liftIO (readMVar x), rhs: Universum.readMVar x + , note: "If you import 'readMVar' from Universum, it's already lifted" } +- warn: { name: "liftIO is not needed", lhs: liftIO (swapMVar x y), rhs: Universum.swapMVar x y + , note: "If you import 'swapMVar' from Universum, it's already lifted" } +- warn: { name: "liftIO is not needed", lhs: liftIO (takeMVar x), rhs: Universum.takeMVar x + , note: "If you import 'takeMVar' from Universum, it's already lifted" } +- warn: { name: "liftIO is not needed", lhs: liftIO (tryPutMVar x y), rhs: Universum.tryPutMVar x y + , note: "If you import 'tryPutMVar' from Universum, it's already lifted" } +- warn: { name: "liftIO is not needed", lhs: liftIO (tryReadMVar x), rhs: Universum.tryReadMVar x + , note: "If you import 'tryReadMVar' from Universum, it's already lifted" } +- warn: { name: "liftIO is not needed", lhs: liftIO (tryTakeMVar x), rhs: Universum.tryTakeMVar x + , note: "If you import 'tryTakeMVar' from Universum, it's already lifted" } +- warn: { name: "liftIO is not needed", lhs: liftIO (atomically x), rhs: Universum.atomically x + , note: "If you import 'atomically' from Universum, it's already lifted" } +- warn: { name: "liftIO is not needed", lhs: liftIO (newTVarIO x), rhs: Universum.newTVarIO x + , note: "If you import 'newTVarIO' from Universum, it's already lifted" } +- warn: { name: "liftIO is not needed", lhs: liftIO (readTVarIO x), rhs: Universum.readTVarIO x + , note: "If you import 'readTVarIO' from Universum, it's already lifted" } + +## IORef + +- warn: { name: "liftIO is not needed", lhs: liftIO (newIORef x), rhs: Universum.newIORef x + , note: "If you import 'newIORef' from Universum, it's already lifted" } +- warn: { name: "liftIO is not needed", lhs: liftIO (readIORef x), rhs: Universum.readIORef x + , note: "If you import 'readIORef' from Universum, it's already lifted" } +- warn: { name: "liftIO is not needed", lhs: liftIO (writeIORef x y), rhs: Universum.writeIORef x y + , note: "If you import 'writeIORef' from Universum, it's already lifted" } +- warn: { name: "liftIO is not needed", lhs: liftIO (modifyIORef x y), rhs: Universum.modifyIORef x y + , note: "If you import 'modifyIORef' from Universum, it's already lifted" } +- warn: { name: "liftIO is not needed", lhs: "liftIO (modifyIORef' x y)", rhs: "Universum.modifyIORef' x y" + , note: "If you import 'modifyIORef'' from Universum, it's already lifted" } +- warn: { name: "liftIO is not needed", lhs: liftIO (atomicModifyIORef x y), rhs: Universum.atomicModifyIORef x y + , note: "If you import 'atomicModifyIORef' from Universum, it's already lifted" } +- warn: { name: "liftIO is not needed", lhs: "liftIO (atomicModifyIORef' x y)", rhs: "Universum.atomicModifyIORef' x y" + , note: "If you import 'atomicModifyIORef'' from Universum, it's already lifted" } +- warn: { name: "liftIO is not needed", lhs: liftIO (atomicWriteIORef x y), rhs: Universum.atomicWriteIORef x y + , note: "If you import 'atomicWriteIORef' from Universum, it's already lifted" } + +## others + +- warn: { name: "liftIO is not needed", lhs: liftIO Universum.getLine, rhs: Universum.getLine + , note: "If you import 'getLine' from Universum, it's already lifted" } +- warn: { name: "liftIO is not needed", lhs: liftIO (Universum.readFile x), rhs: Universum.readFile x + , note: "If you import 'readFile' from Universum, it's already lifted" } +- warn: { name: "liftIO is not needed", lhs: liftIO (Universum.writeFile x y), rhs: Universum.writeFile x y + , note: "If you import 'writeFile' from Universum, it's already lifted" } +- warn: { name: "liftIO is not needed", lhs: liftIO (Universum.appendFile x y), rhs: Universum.appendFile x y + , note: "If you import 'appendFile' from Universum, it's already lifted" } +- warn: { name: "liftIO is not needed", lhs: liftIO (Universum.openFile x y), rhs: Universum.openFile x y + , note: "If you import 'openFile' from Universum, it's already lifted" } +- warn: { name: "liftIO is not needed", lhs: liftIO (exitWith x), rhs: Universum.exitWith x + , note: "If you import 'exitWith' from Universum, it's already lifted" } +- warn: { name: "liftIO is not needed", lhs: liftIO exitFailure, rhs: Universum.exitFailure + , note: "If you import 'exitFailure' from Universum, it's already lifted" } +- warn: { name: "liftIO is not needed", lhs: liftIO exitSuccess, rhs: Universum.exitSuccess + , note: "If you import 'exitSuccess' from Universum, it's already lifted" } +- warn: { name: "liftIO is not needed", lhs: liftIO (die x), rhs: Universum.die x + , note: "If you import 'die' from Universum, it's already lifted" } +- warn: { name: "liftIO is not needed", lhs: liftIO (stToIO x), rhs: Universum.stToIO x + , note: "If you import 'stToIO' from Universum, it's already lifted" } + diff --git a/.workflows/save-work-after b/.workflows/save-work-after index 1c7b6a1..d09853c 100755 --- a/.workflows/save-work-after +++ b/.workflows/save-work-after @@ -1,2 +1,2 @@ #!/usr/bin/env bash -./workflows ci +# ./workflows cidated upstream diff --git a/.workflows/save-work-ahead b/.workflows/save-work-ahead index 0920f8e..12a026d 100755 --- a/.workflows/save-work-ahead +++ b/.workflows/save-work-ahead @@ -1,2 +1,4 @@ #!/usr/bin/env bash -./workflows generate-docs +# ./workflows generate-docs +stack build --no-haddock --only-dependencies --fast --pedantic +stack test --fast --pedantic diff --git a/Setup.hs b/Setup.hs new file mode 100644 index 0000000..9a994af --- /dev/null +++ b/Setup.hs @@ -0,0 +1,2 @@ +import Distribution.Simple +main = defaultMain diff --git a/app/Main.hs b/app/Main.hs new file mode 100644 index 0000000..9f212fb --- /dev/null +++ b/app/Main.hs @@ -0,0 +1,7 @@ +module Main (main) where + +import Lib +import Universum + +main :: IO () +main = runCli diff --git a/elegant-git.cabal b/elegant-git.cabal new file mode 100644 index 0000000..4da9ee1 --- /dev/null +++ b/elegant-git.cabal @@ -0,0 +1,121 @@ +cabal-version: 1.12 + +-- This file has been generated from package.yaml by hpack version 0.35.0. +-- +-- see: https://github.com/sol/hpack + +name: elegant-git +version: 1.0.0 +description: Elegant Git is an assistant who carefully automates routine work with Git. See more on +homepage: https://github.com/bees-hive/elegant-git#readme +bug-reports: https://github.com/bees-hive/elegant-git/issues +author: Dmytro Serdiuk +maintainer: dmytro.serdiuk@gmail.com +copyright: 2017 Dmytro Serdiuk +license: MIT +license-file: LICENSE +build-type: Simple +extra-source-files: + README.md + LICENSE + +source-repository head + type: git + location: https://github.com/bees-hive/elegant-git + +library + exposed-modules: + Elegit.Cli.Action.ShowWork + Elegit.Cli.Command + Elegit.Cli.Parser + Elegit.Git.Action + Elegit.Git.Runner.Real + Elegit.Git.Runner.Simulated + Lib + other-modules: + Paths_elegant_git + hs-source-dirs: + src + default-extensions: + FlexibleContexts + OverloadedStrings + NoImplicitPrelude + ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Werror=incomplete-patterns -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints + build-depends: + base >=4.7 && <5 + , dlist + , fmt + , free + , microlens + , microlens-mtl + , microlens-th + , mtl + , optparse-applicative + , safe-exceptions + , text + , transformers + , typed-process + , universum + default-language: Haskell2010 + +executable git-elegant + main-is: Main.hs + other-modules: + Paths_elegant_git + hs-source-dirs: + app + default-extensions: + FlexibleContexts + OverloadedStrings + NoImplicitPrelude + ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Werror=incomplete-patterns -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N + build-depends: + base >=4.7 && <5 + , dlist + , elegant-git + , fmt + , free + , microlens + , microlens-mtl + , microlens-th + , mtl + , optparse-applicative + , safe-exceptions + , text + , transformers + , typed-process + , universum + default-language: Haskell2010 + +test-suite elegant-git-test + type: exitcode-stdio-1.0 + main-is: Spec.hs + other-modules: + Elegit.Cli.Action.ShowWorkSpec + Elegit.Git.Runner.SimulatedSpec + Paths_elegant_git + hs-source-dirs: + test + default-extensions: + FlexibleContexts + OverloadedStrings + NoImplicitPrelude + ghc-options: -Wall -Wcompat -Widentities -Wincomplete-record-updates -Werror=incomplete-patterns -Wmissing-home-modules -Wpartial-fields -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N + build-depends: + base >=4.7 && <5 + , dlist + , elegant-git + , fmt + , free + , hspec + , microlens + , microlens-mtl + , microlens-th + , mtl + , optparse-applicative + , safe-exceptions + , text + , transformers + , typed-process + , universum + default-language: Haskell2010 diff --git a/elegant-git.iml b/elegant-git.iml new file mode 100644 index 0000000..4254fd5 --- /dev/null +++ b/elegant-git.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/hie.yaml b/hie.yaml new file mode 100644 index 0000000..dfd28ee --- /dev/null +++ b/hie.yaml @@ -0,0 +1,13 @@ +cradle: + stack: + - path: "./src" + component: "elegant-git:lib" + + - path: "./app/Main.hs" + component: "elegant-git:exe:elegant-git-exe" + + - path: "./app/Paths_elegant_git.hs" + component: "elegant-git:exe:elegant-git-exe" + + - path: "./test" + component: "elegant-git:test:elegant-git-test" diff --git a/package.yaml b/package.yaml new file mode 100644 index 0000000..1ad526f --- /dev/null +++ b/package.yaml @@ -0,0 +1,79 @@ +name: elegant-git +version: 1.0.0 +github: "bees-hive/elegant-git" +license: MIT +author: "Dmytro Serdiuk" +maintainer: "dmytro.serdiuk@gmail.com" +copyright: "2017 Dmytro Serdiuk" + +extra-source-files: +- README.md +- LICENSE + +# Metadata used when publishing your package +# synopsis: Short description of your package +# category: Web + +# To avoid duplicated efforts in documentation and dealing with the +# complications of embedding Haddock markup inside cabal files, it is +# common to point users to the README.md file. +description: Elegant Git is an assistant who carefully automates routine work with Git. See more on + +dependencies: +- base >= 4.7 && < 5 + +- universum +- text +- safe-exceptions +- fmt +- transformers +- mtl +- free +- dlist +- microlens +- microlens-mtl +- microlens-th + +- optparse-applicative +- typed-process + +default-extensions: +- FlexibleContexts +- OverloadedStrings +- NoImplicitPrelude + +ghc-options: +- -Wall +- -Wcompat +- -Widentities +- -Wincomplete-record-updates +- -Werror=incomplete-patterns +- -Wmissing-home-modules +- -Wpartial-fields +- -Wredundant-constraints + +library: + source-dirs: src + +executables: + git-elegant: + main: Main.hs + source-dirs: app + ghc-options: + - -threaded + - -rtsopts + - -with-rtsopts=-N + dependencies: + - elegant-git + +tests: + elegant-git-test: + main: Spec.hs + source-dirs: test + ghc-options: + - -threaded + - -rtsopts + - -with-rtsopts=-N + dependencies: + - elegant-git + - hspec diff --git a/src/Elegit/Cli/Action/ShowWork.hs b/src/Elegit/Cli/Action/ShowWork.hs new file mode 100644 index 0000000..116a4c5 --- /dev/null +++ b/src/Elegit/Cli/Action/ShowWork.hs @@ -0,0 +1,39 @@ +module Elegit.Cli.Action.ShowWork where + +import Control.Monad.Free.Class +import qualified Data.Text as T +import qualified Elegit.Git.Action as GA +import Fmt +import Universum + +-- | Exectuion description of the AquireRepository action +cmd :: (MonadFree GA.GitF m) => m () +cmd = do + currentBranch <- GA.currentBranch + mCurrentUpstream <- GA.branchUpstream currentBranch + branchWithLatestChanges <- GA.freshestDefaultBranch + logs <- GA.log GA.LogOneLine branchWithLatestChanges currentBranch + changes <- GA.status GA.StatusShort + stashes <- GA.stashList + + GA.reportInfo ">>> Branch refs:" + GA.reportInfo (fmt "local: "+|currentBranch|+"") + case mCurrentUpstream of + Just currentUpstream -> GA.reportInfo (fmt "remote: "+|currentUpstream|+"") + Nothing -> pass + + GA.reportInfo "" + + unless (null logs) $ do + GA.reportInfo (fmt ">>> New commits (comparing to "+|branchWithLatestChanges|+" branch):") + GA.print $ T.intercalate "\n" logs + GA.reportInfo "" + + unless (null changes) $ do + GA.reportInfo ">>> Uncommitted modifications:" + GA.print $ T.intercalate "\n" changes + GA.reportInfo "" + + unless (null stashes) $ do + GA.reportInfo ">>> Available stashes:" + GA.print $ T.intercalate "\n" stashes diff --git a/src/Elegit/Cli/Command.hs b/src/Elegit/Cli/Command.hs new file mode 100644 index 0000000..24a2eb3 --- /dev/null +++ b/src/Elegit/Cli/Command.hs @@ -0,0 +1,4 @@ +module Elegit.Cli.Command where + +data ElegitCommand + = ShowWorkCommand diff --git a/src/Elegit/Cli/Parser.hs b/src/Elegit/Cli/Parser.hs new file mode 100644 index 0000000..8f07440 --- /dev/null +++ b/src/Elegit/Cli/Parser.hs @@ -0,0 +1,26 @@ +module Elegit.Cli.Parser where + +import Elegit.Cli.Command (ElegitCommand (ShowWorkCommand)) +import Options.Applicative +import Universum + + +type Command a = Mod CommandFields a + + +showWorkCommand :: Command ElegitCommand +showWorkCommand = + command "show-work" $ + flip info (progDesc "Prints HEAD state.") $ pure ShowWorkCommand + + +dayToDayContributionsCommand :: Command ElegitCommand +dayToDayContributionsCommand = + commandGroup " make day-to-day contributions" + <> showWorkCommand + + +cli :: ParserInfo ElegitCommand +cli = flip info mempty $ + hsubparser dayToDayContributionsCommand <**> helper + diff --git a/src/Elegit/Git/Action.hs b/src/Elegit/Git/Action.hs new file mode 100644 index 0000000..d6f86f6 --- /dev/null +++ b/src/Elegit/Git/Action.hs @@ -0,0 +1,139 @@ +{-# LANGUAGE DeriveFunctor #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE FlexibleContexts #-} + +----------------------------------------------------------------------------- +-- | +-- Module : Elegit.Git.Action +-- +-- This module provides the way to build the exectuion tree for any action +-- we can think of. +-- To make the life easier for us, we can leverage the functional concept +-- "Free Monad". `Free` gives us a way to lift any `Functor f` to use it in the +-- monadic context. +-- +-- The cool thing about `Free`, is that it's represented as tree of arbitrarely +-- nested computations. +-- +-- This feature of `Free` gives us an easy way to describe the potential execution +-- and build any number of interpreters we want. This way we can mock any command +-- for testing purposes or create the summary of the commands we will execute. +-- +-- Some notes: +-- +-- * "Church" is the implementation of the `Free` which has a better performance +-- in the process of building the tree. Basically we use fancy way to build +-- our tree only once. +-- +----------------------------------------------------------------------------- +module Elegit.Git.Action where + + +import Control.Monad.Free +import Control.Monad.Free.Church +import Universum + +-- | The declaration of all posible actions we can do in the git action. +-- +-- This describes the data of the action, and whether it can return any value +-- for further computations. +-- +-- We can use records later to better comunicate the purpose of each field by +-- providing a name. +-- +-- === How to understand this structure: +-- +-- Each case consists of the data and the return type +-- +-- * @CurrentBranch (Text -> a)@: +-- The `CurrentBranch` has no data associated with it, because we have only 1 field +-- and the last field indicates the result type +-- In the @Text -> a@ we specify that we provide the value of type `Text` to the +-- next computation. TLDR: If you want to provide value of type @t@ from your command +-- directly - follow the @(t -> a)@ placeholder. If you don't provide any value, you +-- should use the @(() -> a)@ type. +-- In haskell @()@ represents the type `Unit` and has only one value @()@. +-- +-- * @UpdateConfig Text Text (() -> a)@: +-- Here you can see that the action `UpdateConfig` has 2 fields of type `Text` and +-- no return value. +-- Note as Haskell is lazy, you can simplify any function of type @() -> a@ to just @a@. +-- +-- TODO: Use records +data GitF a + = CurrentBranch (Text -> a) + | BranchUpstream Text (Maybe Text -> a) + | Log LogType Text Text ([Text] -> a) + | Status StatusType ([Text] -> a) + | StashList ([Text] -> a) + + | ReportInfo Text a + | PrintText Text a + deriving stock (Functor) + + +-- | Represents types of git status output +-- +-- `StatusShort` is the same as "--short" option. +data StatusType + = StatusShort + + +-- | Represents types of git log output +-- +-- `LogOneLine` is the same as "--oneline" option. +data LogType + = LogOneLine + + +-- | Type alias to the `Free` `GitF` monad. +type FreeGit t = F GitF t + +-- | You should consider following code as a boilerplate +-- +-- Each command should have the associated function to simplify the usage of this API. +-- This can be generated with haskell `TemplateHaskell` feature, but it's better to have +-- it here to understand what we do here +-- +-- Every function just tells us that we can use them in any @Monad m@ which has a +-- `MonadFree` implementation. This is just a fancy way to make the code more generic in +-- terms of execution context. +-- +-- Some notes: +-- +-- * When our function has a continuation of type @(() -> a)@, you simply pass @()@ as the +-- value. +-- * Otherwise just use `id` function. + +status :: (MonadFree GitF m) => StatusType -> m [Text] +status sType = liftF $ Status sType id + +log :: (MonadFree GitF m) => LogType -> Text -> Text -> m [Text] +log lType lBase lTarget = liftF $ Log lType lBase lTarget id + +stashList :: (MonadFree GitF m) => m [Text] +stashList = liftF $ StashList id + +currentBranch :: (MonadFree GitF m) => m Text +currentBranch = liftF $ CurrentBranch id + +branchUpstream :: (MonadFree GitF m) => Text -> m (Maybe Text) +branchUpstream bName = liftF $ BranchUpstream bName id + +-- | +-- +-- Maybe we should not report content, but rather format it as info. +reportInfo :: (MonadFree GitF m) => Text -> m () +reportInfo content = liftF $ ReportInfo content () + +print :: (MonadFree GitF m) => Text -> m () +print content = liftF $ PrintText content () + + +-- Derived actions + + +freshestDefaultBranch :: (MonadFree GitF m) => m Text +freshestDefaultBranch = do + -- TODO: Port elegant git logic + return "main" diff --git a/src/Elegit/Git/Runner/Real.hs b/src/Elegit/Git/Runner/Real.hs new file mode 100644 index 0000000..d00d1c8 --- /dev/null +++ b/src/Elegit/Git/Runner/Real.hs @@ -0,0 +1,69 @@ +module Elegit.Git.Runner.Real where + +import Control.Exception.Safe (throwString) +import Control.Monad.Free.Church +import Data.Text (stripEnd) +import qualified Elegit.Git.Action as GA +import Fmt +import System.Process.Typed (ExitCode (ExitFailure, ExitSuccess), + proc, readProcess) +import Universum + +runGit :: (MonadCatch m, MonadIO m) => Text -> m (Maybe Text) +runGit cmd = do + (eCode, outputBS, _errBS) <- readProcess $ proc "git" (toString <$> words cmd) + case eCode of + ExitFailure _ -> do + -- TODO: Know what error is expected and log unexpected ones. + return Nothing + ExitSuccess -> do + let output = stripEnd $ decodeUtf8 outputBS + return $ if null output then Nothing else Just output + +-- | Execute the action in the real world. +executeGit :: (MonadCatch m, MonadIO m) => GA.FreeGit () -> m () +executeGit = foldF executeGitF + +-- | Interpreter for the real world +-- +-- Currently just prints the resulting commands without any execution. +-- +executeGitF :: (MonadCatch m, MonadIO m) => GA.GitF a -> m a +executeGitF arg = case arg of + GA.CurrentBranch next -> do + mCurrentBranch <- runGit "rev-parse --abbrev-ref @" + case mCurrentBranch of + Nothing -> throwString "No current branch found" + Just currentBranch -> + return $ next currentBranch + + GA.BranchUpstream branch next -> do + mUpstreamBranch <- runGit (fmt "rev-parse --abbrev-ref "+|branch|+"@{upstream}") + return $ next mUpstreamBranch + + GA.Log lType base target next -> do + let + logArg :: Text + logArg = case lType of + GA.LogOneLine -> "--oneline" + + logs <- lines . fromMaybe "" <$> runGit (fmt "-c color.ui=always log "+|logArg|+" "+|base|+".."+|target|+"") + return $ next logs + GA.Status sType next -> do + let + statusFormat :: Text + statusFormat = case sType of + GA.StatusShort -> "--short" + + changes <- lines . fromMaybe "" <$> runGit (fmt "-c color.status=always status "+|statusFormat|+"") + return $ next changes + GA.StashList next -> do + stashes <- lines . fromMaybe "" <$> runGit "stash list" + return $ next stashes + + GA.ReportInfo content next -> do + putTextLn (fmt "\x1b[32m"+|content|+"\x1b[0m") + return next + GA.PrintText content next -> do + putTextLn content + return next diff --git a/src/Elegit/Git/Runner/Simulated.hs b/src/Elegit/Git/Runner/Simulated.hs new file mode 100644 index 0000000..30a0df4 --- /dev/null +++ b/src/Elegit/Git/Runner/Simulated.hs @@ -0,0 +1,154 @@ +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE TemplateHaskell #-} +module Elegit.Git.Runner.Simulated where + +import Control.Monad.Free.Church +import Control.Monad.Writer.Strict +import Data.DList as DList +import qualified Data.List.NonEmpty as NE +import qualified Elegit.Git.Action as GA +import Fmt +import Lens.Micro +import Lens.Micro.TH +import Universum as U + +-- | Describes all the metrics we collect from the git action execution +data GitCommand + = UpdateConfigCommand Text Text + | CloneRepositoryCommand Text + | ReportInfo Text + | PrintText Text + deriving stock (Show, Eq) + +-- TODO: Add commit hash. +newtype GCommit + = GCommit + { _gcName :: Text + } + deriving (Show, Eq) + +makeLenses ''GCommit + + +data GBranch + = GBranch + { _gbName :: Text + , _gbUpstream :: Maybe Text + , _gbCommit :: NonEmpty GCommit + } + deriving (Show, Eq) + +makeLenses ''GBranch + + +data GStash + = GStash + { _gsName :: Text + , _gsBranchName :: Text + } + deriving (Show, Eq) + +makeLenses ''GStash + + +newtype GRemote + = GRemote + { _grmName :: Text + -- , _grmUrl :: Text + } + deriving (Show, Eq) + +makeLenses ''GRemote + + +data GRepository + = GRepository + { _grRemotes :: [GRemote] + , _grBranches :: [GBranch] + , _grCurrentBranch :: Text + , _grStashes :: [GStash] + , _grModifiedFiles :: [Text] + , _grUnstagedFiles :: [Text] + } + deriving (Show, Eq) + + +-- greatestCommonAncestor :: [GCommit] -> [GCommit] -> Maybe GCommit +-- greatestCommonAncestor left right = + + + +commitDifference :: GCommit -> [GCommit] -> [GCommit] +commitDifference _ [] = [] +commitDifference bc (tc : tcs) + | tc^.gcName == bc^.gcName = [] + | otherwise = tc: commitDifference bc tcs + + +makeLenses ''GRepository + + +-- | Collects a summary of execution as a list of `GitCommand`s. +runGitActionPure :: GRepository -> GA.FreeGit () -> (GRepository, [GitCommand]) +runGitActionPure gr action = + let + (commands, repo) = runIdentity $ flip runStateT gr $ execWriterT $ foldF collectImpureCommandsF action + in (repo, DList.toList commands) + +-- | Interpreter of GitF which collects the summary of exection in `Writer` monad. +-- +-- Each branch should return the value of type a, which can be obtained by calling +-- the `next` function. +-- +-- We use `tell` function to store the command in `DList GitCommand`. `DList` can be treated +-- as just plain list (@[]@) but that has O(1) `append` operation instead of the O(n) of the @[]@. +-- +-- Note that we are in the context of the `Writer` monad and we need to wrap the value `a` in +-- `Writer`. To lift any value into a monad you should use `return`. +collectImpureCommandsF :: (MonadState GRepository m, MonadWriter (DList GitCommand) m) => GA.GitF a -> m a +collectImpureCommandsF cmd = case cmd of + GA.CurrentBranch next -> do + currentBranchName <- use grCurrentBranch + return $ next currentBranchName + GA.BranchUpstream branch next -> do + branches <- use grBranches + return $ next (find (\b -> b^.gbName == branch) branches >>= _gbUpstream) + + GA.Log lType base target next -> do + case lType of + GA.LogOneLine -> do + mBaseBranch <- preuse $ grBranches . each . filtered (\b -> b ^. gbName == base) + mTargetBranch <- preuse $ grBranches . each . filtered (\b -> b ^. gbName == target) + + return $ next $ fromMaybe [] $ do + baseBranch <- mBaseBranch + targetBranch <- mTargetBranch + let baseBranchHead = baseBranch ^. gbCommit.to U.head + return $ view gcName <$> commitDifference baseBranchHead (NE.toList $ targetBranch^.gbCommit) + + GA.Status sType next -> do + case sType of + GA.StatusShort -> do + modifiedFiles <- use grModifiedFiles + unstagedFiles <- use grUnstagedFiles + let + modified :: [Text] + modified = (\modifiedFile -> fmt "M "+|modifiedFile|+"") <$> modifiedFiles + unstaged :: [Text] + unstaged = (\unstagedFile -> fmt "?? "+|unstagedFile|+"") <$> unstagedFiles + return $ next (modified <> unstaged) + + GA.StashList next -> do + stashes <- use grStashes + return $ next + [ fmt "stash@{"+||i||+"}: "+|(stash^.gsName)|+" on "+|(stash^.gsBranchName)|+"" | (i, stash) <- zip [(0 :: Int)..] stashes + ] -- this is excessive, I guess? @teggotic + + GA.ReportInfo content next -> do + tell $ singleton $ ReportInfo content + return next + GA.PrintText content next -> do + -- make each line a separate print to make it easier to write test cases + tell $ DList.fromList (PrintText <$> lines content) + return next diff --git a/src/Lib.hs b/src/Lib.hs new file mode 100644 index 0000000..e692ae4 --- /dev/null +++ b/src/Lib.hs @@ -0,0 +1,21 @@ +module Lib where + +import qualified Elegit.Cli.Action.ShowWork as ShowWork +import Elegit.Cli.Command (ElegitCommand (..)) +import qualified Elegit.Cli.Parser as P +import Elegit.Git.Runner.Real (executeGit) +import Options.Applicative (customExecParser, prefs, + showHelpOnEmpty, showHelpOnError) +import Universum + +runCli :: (MonadIO m, MonadCatch m) => m () +runCli = do + let + cliPrefs = prefs $ showHelpOnError <> showHelpOnEmpty + + cmd <- liftIO $ customExecParser cliPrefs P.cli + + flip catch (\e -> putTextLn $ "Caught exception: " <> show (e :: SomeException) ) $ + executeGit $ + case cmd of + ShowWorkCommand -> ShowWork.cmd diff --git a/stack.yaml b/stack.yaml new file mode 100644 index 0000000..f43114a --- /dev/null +++ b/stack.yaml @@ -0,0 +1,67 @@ +# This file was automatically generated by 'stack init' +# +# Some commonly used options have been documented as comments in this file. +# For advanced use and comprehensive documentation of the format, please see: +# https://docs.haskellstack.org/en/stable/yaml_configuration/ + +# Resolver to choose a 'specific' stackage snapshot or a compiler version. +# A snapshot resolver dictates the compiler version and the set of packages +# to be used for project dependencies. For example: +# +# resolver: lts-3.5 +# resolver: nightly-2015-09-21 +# resolver: ghc-9.4.3 +# +# The location of a snapshot can be provided as a file or url. Stack assumes +# a snapshot provided as a file might change, whereas a url resource does not. +# +# resolver: ./custom-snapshot.yaml +# resolver: https://example.com/snapshots/2018-01-01.yaml +resolver: + url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/19/22.yaml + +# User packages to be built. +# Various formats can be used as shown in the example below. +# +# packages: +# - some-directory +# - https://example.com/foo/bar/baz-0.0.2.tar.gz +# subdirs: +# - auto-update +# - wai +packages: +- . +# Dependency packages to be pulled from upstream that are not in the resolver. +# These entries can reference officially published versions as well as +# forks / in-progress versions pinned to a git hash. For example: +# +# extra-deps: +# - acme-missiles-0.3 +# - git: https://github.com/commercialhaskell/stack.git +# commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a +# +# extra-deps: [] + +# Override default flag values for local packages and extra-deps +# flags: {} + +# Extra package databases containing global packages +# extra-package-dbs: [] + +# Control whether we use the GHC we find on the path +# system-ghc: true +# +# Require a specific version of stack, using version ranges +# require-stack-version: -any # Default +# require-stack-version: ">=2.7" +# +# Override the architecture used by stack, especially useful on Windows +# arch: i386 +# arch: x86_64 +# +# Extra directories used by stack for building +# extra-include-dirs: [/path/to/dir] +# extra-lib-dirs: [/path/to/dir] +# +# Allow a newer minor version of GHC than the snapshot specifies +# compiler-check: newer-minor diff --git a/stack.yaml.lock b/stack.yaml.lock new file mode 100644 index 0000000..b90e71d --- /dev/null +++ b/stack.yaml.lock @@ -0,0 +1,13 @@ +# This file was autogenerated by Stack. +# You should not edit this file by hand. +# For more information, please see the documentation at: +# https://docs.haskellstack.org/en/stable/lock_files + +packages: [] +snapshots: +- completed: + sha256: 5098594e71bdefe0c13e9e6236f12e3414ef91a2b89b029fd30e8fc8087f3a07 + size: 619399 + url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/19/22.yaml + original: + url: https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/19/22.yaml diff --git a/test/Elegit/Cli/Action/ShowWorkSpec.hs b/test/Elegit/Cli/Action/ShowWorkSpec.hs new file mode 100644 index 0000000..c8480ff --- /dev/null +++ b/test/Elegit/Cli/Action/ShowWorkSpec.hs @@ -0,0 +1,105 @@ +module Elegit.Cli.Action.ShowWorkSpec where + +import qualified Data.List.NonEmpty as NE +import qualified Elegit.Cli.Action.ShowWork as ShowWork +import Elegit.Git.Runner.Simulated +import Lens.Micro +import Test.Hspec +import Universum + + +defaultRepository :: GRepository +defaultRepository = + let + commit = GCommit + { _gcName = "Init commit" + } + mainBranch = GBranch + { _gbName = "main" + , _gbUpstream = Nothing + , _gbCommit = pure commit + } + currentBranch = GBranch + { _gbName = "haskell" + , _gbUpstream = Nothing + , _gbCommit = pure commit + } + in GRepository + { _grRemotes = [] + , _grBranches = [mainBranch, currentBranch] + , _grCurrentBranch = currentBranch^.gbName + , _grModifiedFiles = [] + , _grUnstagedFiles = [] + , _grStashes = [ ] + } + +spec :: Spec +spec = do + describe "cmd" $ do + it "prints state of HEAD branch" $ do + runGitActionPure defaultRepository ShowWork.cmd `shouldBe` + ( defaultRepository + , [ ReportInfo ">>> Branch refs:" + , ReportInfo "local: haskell" + , ReportInfo "" + ] + ) + it "print status when files changed" $ do + let + repo = defaultRepository & grModifiedFiles .~ ["app/Main.hs"] + & grUnstagedFiles .~ ["tmp.txt"] + runGitActionPure repo ShowWork.cmd `shouldBe` + ( repo + , [ ReportInfo ">>> Branch refs:" + , ReportInfo "local: haskell" + , ReportInfo "" + , ReportInfo ">>> Uncommitted modifications:" + , PrintText "M app/Main.hs" + , PrintText "?? tmp.txt" + , ReportInfo "" + ] + ) + it "prints remote" $ do + let + repo = defaultRepository & grBranches.mapped.gbUpstream ?~ "origin/haskell" + runGitActionPure repo ShowWork.cmd `shouldBe` + ( repo + , [ ReportInfo ">>> Branch refs:" + , ReportInfo "local: haskell" + , ReportInfo "remote: origin/haskell" + , ReportInfo "" + ] + ) + it "prints stash" $ do + let + stash = GStash + { _gsName = "WIP" + , _gsBranchName = "haskell" + } + repo = defaultRepository & grStashes .~ [stash] + + runGitActionPure repo ShowWork.cmd `shouldBe` + ( repo + , [ ReportInfo ">>> Branch refs:" + , ReportInfo "local: haskell" + , ReportInfo "" + , ReportInfo ">>> Available stashes:" + , PrintText "stash@{0}: WIP on haskell" + ] + ) + it "prints log" $ do + let + newCommit = GCommit {_gcName = "Updates"} + + repo = defaultRepository & grBranches . each . filtered ((== "haskell") . view gbName) . gbCommit %~ NE.cons newCommit + + runGitActionPure repo ShowWork.cmd `shouldBe` + ( repo + , [ ReportInfo ">>> Branch refs:" + , ReportInfo "local: haskell" + , ReportInfo "" + , ReportInfo ">>> New commits (comparing to main branch):" + , PrintText "Updates" + , ReportInfo "" + ] + ) diff --git a/test/Elegit/Git/Runner/SimulatedSpec.hs b/test/Elegit/Git/Runner/SimulatedSpec.hs new file mode 100644 index 0000000..704564a --- /dev/null +++ b/test/Elegit/Git/Runner/SimulatedSpec.hs @@ -0,0 +1,20 @@ +module Elegit.Git.Runner.SimulatedSpec where +import Test.Hspec +import Universum + +import Elegit.Git.Runner.Simulated + +spec :: Spec +spec = do + describe "git commit diff" $ do + it "should show no difference" $ do + let + commit = GCommit + { _gcName = "Initial" + } + commitDifference commit [commit] `shouldBe` [] + it "should show 1 commit difference" $ do + let + baseCommit = GCommit { _gcName = "Initial" } + updateCommit = GCommit { _gcName = "Update" } + commitDifference baseCommit [updateCommit, baseCommit] `shouldBe` [updateCommit] diff --git a/test/Spec.hs b/test/Spec.hs new file mode 100644 index 0000000..a824f8c --- /dev/null +++ b/test/Spec.hs @@ -0,0 +1 @@ +{-# OPTIONS_GHC -F -pgmF hspec-discover #-}