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 #-}