diff --git a/.obelisk/impl/default.nix b/.obelisk/impl/default.nix new file mode 100644 index 00000000..2b4d4ab1 --- /dev/null +++ b/.obelisk/impl/default.nix @@ -0,0 +1,2 @@ +# DO NOT HAND-EDIT THIS FILE +import (import ./thunk.nix) \ No newline at end of file diff --git a/.obelisk/impl/github.json b/.obelisk/impl/github.json new file mode 100644 index 00000000..00006b2e --- /dev/null +++ b/.obelisk/impl/github.json @@ -0,0 +1,8 @@ +{ + "owner": "obsidiansystems", + "repo": "obelisk", + "branch": "release/0.9.0.1", + "private": false, + "rev": "11beb6e8cd2419b2429925b76a98f24035e40985", + "sha256": "0b4m33b7yyzsbkvfz2kwg4v9hlnvbjlmjikbvwd7pg52vy84and0" +} diff --git a/.obelisk/impl/thunk.nix b/.obelisk/impl/thunk.nix new file mode 100644 index 00000000..bbf2dc18 --- /dev/null +++ b/.obelisk/impl/thunk.nix @@ -0,0 +1,9 @@ +# DO NOT HAND-EDIT THIS FILE +let fetch = { private ? false, fetchSubmodules ? false, owner, repo, rev, sha256, ... }: + if !fetchSubmodules && !private then builtins.fetchTarball { + url = "https://github.com/${owner}/${repo}/archive/${rev}.tar.gz"; inherit sha256; + } else (import {}).fetchFromGitHub { + inherit owner repo rev sha256 fetchSubmodules private; + }; + json = builtins.fromJSON (builtins.readFile ./github.json); +in fetch json \ No newline at end of file diff --git a/apps/kassandra/.gitignore b/apps/kassandra/.gitignore new file mode 100644 index 00000000..a8513a1d --- /dev/null +++ b/apps/kassandra/.gitignore @@ -0,0 +1,14 @@ +.envrc +.direnv +.ghc.environment.x86_64-linux-8.6.5 +dist-newstyle +dist +result +result-* +result-android +result-ios +result-exe +.attr-cache +ghcid-output.txt +profile +hie-output diff --git a/apps/kassandra/.hlint.yaml b/apps/kassandra/.hlint.yaml new file mode 100644 index 00000000..270feee7 --- /dev/null +++ b/apps/kassandra/.hlint.yaml @@ -0,0 +1,3086 @@ +- arguments: + - -XConstraintKinds + - -XDeriveGeneric + - -XGeneralizedNewtypeDeriving + - -XLambdaCase + - -XOverloadedStrings + - -XRecordWildCards + - -XScopedTypeVariables + - -XStandaloneDeriving + - -XTupleSections + - -XTypeApplications + - -XViewPatterns +- ignore: + name: Use head +- ignore: + name: Use Foldable.forM_ +- hint: + lhs: pure () + note: Use 'pass' + rhs: pass +- hint: + lhs: return () + note: Use 'pass' + rhs: pass +- hint: + lhs: '(: [])' + note: Use `one` + rhs: one +- hint: + lhs: (:| []) + note: Use `one` + rhs: one +- hint: + lhs: Data.Sequence.singleton + note: Use `one` + rhs: one +- hint: + lhs: Data.Text.singleton + note: Use `one` + rhs: one +- hint: + lhs: Data.Text.Lazy.singleton + note: Use `one` + rhs: one +- hint: + lhs: Data.ByteString.singleton + note: Use `one` + rhs: one +- hint: + lhs: Data.ByteString.Lazy.singleton + note: Use `one` + rhs: one +- hint: + lhs: Data.Map.singleton + note: Use `one` + rhs: one +- hint: + lhs: Data.Map.Strict.singleton + note: Use `one` + rhs: one +- hint: + lhs: Data.HashMap.Strict.singleton + note: Use `one` + rhs: one +- hint: + lhs: Data.HashMap.Lazy.singleton + note: Use `one` + rhs: one +- hint: + lhs: Data.IntMap.singleton + note: Use `one` + rhs: one +- hint: + lhs: Data.IntMap.Strict.singleton + note: Use `one` + rhs: one +- hint: + lhs: Data.Set.singleton + note: Use `one` + rhs: one +- hint: + lhs: Data.HashSet.singleton + note: Use `one` + rhs: one +- hint: + lhs: Data.IntSet.singleton + note: Use `one` + rhs: one +- 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 +- hint: + lhs: Control.Exception.throw + note: Use 'impureThrow' + rhs: impureThrow +- warn: + lhs: Data.Text.IO.readFile + rhs: readFileText +- warn: + lhs: Data.Text.IO.writeFile + rhs: writeFileText +- warn: + lhs: Data.Text.IO.appendFile + rhs: appendFileText +- warn: + lhs: Data.Text.Lazy.IO.readFile + rhs: readFileLText +- warn: + lhs: Data.Text.Lazy.IO.writeFile + rhs: writeFileLText +- warn: + lhs: Data.Text.Lazy.IO.appendFile + rhs: appendFileLText +- warn: + lhs: Data.ByteString.readFile + rhs: readFileBS +- warn: + lhs: Data.ByteString.writeFile + rhs: writeFileBS +- warn: + lhs: Data.ByteString.appendFile + rhs: appendFileBS +- warn: + lhs: Data.ByteString.Lazy.readFile + rhs: readFileLBS +- warn: + lhs: Data.ByteString.Lazy.writeFile + rhs: writeFileLBS +- warn: + lhs: Data.ByteString.Lazy.appendFile + rhs: appendFileLBS +- hint: + lhs: foldl' (flip f) + note: Use 'flipfoldl'' + rhs: flipfoldl' f +- warn: + lhs: foldl' (+) 0 + rhs: sum +- warn: + lhs: foldl' (*) 1 + rhs: product +- hint: + lhs: fmap and (sequence s) + note: Applying this hint would mean that some actions that were being executed + previously would no longer be executed. + rhs: andM s +- hint: + lhs: and <$> sequence s + note: Applying this hint would mean that some actions that were being executed + previously would no longer be executed. + rhs: andM s +- hint: + lhs: fmap or (sequence s) + note: Applying this hint would mean that some actions that were being executed + previously would no longer be executed. + rhs: orM s +- hint: + lhs: or <$> sequence s + note: Applying this hint would mean that some actions that were being executed + previously would no longer be executed. + rhs: orM s +- hint: + lhs: fmap and (mapM f s) + note: Applying this hint would mean that some actions that were being executed + previously would no longer be executed. + rhs: allM f s +- hint: + lhs: and <$> mapM f s + note: Applying this hint would mean that some actions that were being executed + previously would no longer be executed. + rhs: allM f s +- hint: + lhs: fmap or (mapM f s) + note: Applying this hint would mean that some actions that were being executed + previously would no longer be executed. + rhs: anyM f s +- hint: + lhs: or <$> mapM f s + note: Applying this hint would mean that some actions that were being executed + previously would no longer be executed. + rhs: anyM f s +- warn: + lhs: getAlt (foldMap (Alt . f) xs) + rhs: asumMap xs +- warn: + lhs: getAlt . foldMap (Alt . f) + rhs: asumMap +- hint: + lhs: foldr (\x acc -> f x <|> acc) empty + note: Use 'asumMap' + rhs: asumMap f +- hint: + lhs: asum (map f xs) + note: Use 'asumMap' + rhs: asumMap f xs +- warn: + lhs: map fst &&& map snd + rhs: unzip +- hint: + lhs: fmap (fmap f) x + note: Use '(<<$>>)' + rhs: f <<$>> x +- hint: + lhs: (\f -> f x) <$> ff + note: Use flap operator + rhs: ff ?? x +- hint: + lhs: fmap (\f -> f x) ff + note: Use flap operator + rhs: ff ?? x +- hint: + lhs: fmap ($ x) ff + note: Use flap operator + rhs: ff ?? x +- hint: + lhs: ($ x) <$> ff + note: Use flap operator + rhs: ff ?? x +- warn: + lhs: fmap f (nonEmpty x) + rhs: viaNonEmpty f x +- warn: + lhs: fmap f . nonEmpty + rhs: viaNonEmpty f +- warn: + lhs: f <$> nonEmpty x + rhs: viaNonEmpty f x +- warn: + lhs: f >>= guard + rhs: guardM f +- warn: + lhs: guard =<< f + rhs: guardM f +- 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 l +- warn: + lhs: maybe (Right r) Left + rhs: maybeToLeft r +- warn: + lhs: case m of Just x -> f x; Nothing -> pure () + rhs: whenJust m f +- warn: + lhs: case m of Just x -> f x; Nothing -> return () + rhs: whenJust m f +- warn: + lhs: case m of Just x -> f x; Nothing -> pass + rhs: whenJust m f +- warn: + lhs: case m of Nothing -> pure () ; Just x -> f x + rhs: whenJust m f +- warn: + lhs: case m of Nothing -> return (); Just x -> f x + rhs: whenJust m f +- warn: + lhs: case m of Nothing -> pass ; Just x -> f x + rhs: whenJust m f +- warn: + lhs: maybe (pure ()) f m + rhs: whenJust m f +- warn: + lhs: maybe (return ()) f m + rhs: whenJust m f +- warn: + lhs: maybe pass f m + rhs: whenJust m f +- warn: + lhs: m >>= \a -> whenJust a f + rhs: whenJustM m f +- warn: + lhs: m >>= \case Just x -> f x; Nothing -> pure () + rhs: whenJustM m f +- warn: + lhs: m >>= \case Just x -> f x; Nothing -> return () + rhs: whenJustM m f +- warn: + lhs: m >>= \case Just x -> f x; Nothing -> pass + rhs: whenJustM m f +- warn: + lhs: m >>= \case Nothing -> pure () ; Just x -> f x + rhs: whenJustM m f +- warn: + lhs: m >>= \case Nothing -> return (); Just x -> f x + rhs: whenJustM m f +- warn: + lhs: m >>= \case Nothing -> pass ; Just x -> f x + rhs: whenJustM m f +- warn: + lhs: maybe (pure ()) f =<< m + rhs: whenJustM m f +- warn: + lhs: maybe (return ()) f =<< m + rhs: whenJustM m f +- warn: + lhs: maybe pass f =<< m + rhs: whenJustM m f +- warn: + lhs: m >>= maybe (pure ()) f + rhs: whenJustM m f +- warn: + lhs: m >>= maybe (return ()) f + rhs: whenJustM m f +- warn: + lhs: m >>= maybe pass f + rhs: whenJustM m f +- warn: + lhs: case m of Just _ -> pure () ; Nothing -> x + rhs: whenNothing_ m x +- warn: + lhs: case m of Just _ -> return (); Nothing -> x + rhs: whenNothing_ m x +- warn: + lhs: case m of Just _ -> pass ; Nothing -> x + rhs: whenNothing_ m x +- warn: + lhs: case m of Nothing -> x; Just _ -> pure () + rhs: whenNothing_ m x +- warn: + lhs: case m of Nothing -> x; Just _ -> return () + rhs: whenNothing_ m x +- warn: + lhs: case m of Nothing -> x; Just _ -> pass + rhs: whenNothing_ m x +- warn: + lhs: maybe x (\_ -> pure () ) m + rhs: whenNothing_ m x +- warn: + lhs: maybe x (\_ -> return () ) m + rhs: whenNothing_ m x +- warn: + lhs: maybe x (\_ -> pass ) m + rhs: whenNothing_ m x +- warn: + lhs: maybe x (const (pure () )) m + rhs: whenNothing_ m x +- warn: + lhs: maybe x (const (return ())) m + rhs: whenNothing_ m x +- warn: + lhs: maybe x (const pass) m + rhs: whenNothing_ m x +- warn: + lhs: m >>= \a -> whenNothing_ a x + rhs: whenNothingM_ m x +- warn: + lhs: m >>= \case Just _ -> pure () ; Nothing -> x + rhs: whenNothingM_ m x +- warn: + lhs: m >>= \case Just _ -> return (); Nothing -> x + rhs: whenNothingM_ m x +- warn: + lhs: m >>= \case Just _ -> pass ; Nothing -> x + rhs: whenNothingM_ m x +- warn: + lhs: m >>= \case Nothing -> x; Just _ -> pure () + rhs: whenNothingM_ m x +- warn: + lhs: m >>= \case Nothing -> x; Just _ -> return () + rhs: whenNothingM_ m x +- warn: + lhs: m >>= \case Nothing -> x; Just _ -> pass + rhs: whenNothingM_ m x +- warn: + lhs: maybe x (\_ -> pure () ) =<< m + rhs: whenNothingM_ m x +- warn: + lhs: maybe x (\_ -> return () ) =<< m + rhs: whenNothingM_ m x +- warn: + lhs: maybe x (\_ -> pass ) =<< m + rhs: whenNothingM_ m x +- warn: + lhs: maybe x (const (pure () )) =<< m + rhs: whenNothingM_ m x +- warn: + lhs: maybe x (const (return ())) =<< m + rhs: whenNothingM_ m x +- warn: + lhs: maybe x (const pass) =<< m + rhs: whenNothingM_ m x +- warn: + lhs: m >>= maybe x (\_ -> pure ()) + rhs: whenNothingM_ m x +- warn: + lhs: m >>= maybe x (\_ -> return ()) + rhs: whenNothingM_ m x +- warn: + lhs: m >>= maybe x (\_ -> pass) + rhs: whenNothingM_ m x +- warn: + lhs: m >>= maybe x (const (pure ()) ) + rhs: whenNothingM_ m x +- warn: + lhs: m >>= maybe x (const (return ())) + rhs: whenNothingM_ m x +- warn: + lhs: m >>= maybe x (const pass) + rhs: whenNothingM_ m x +- warn: + lhs: whenLeft () + rhs: whenLeft_ +- warn: + lhs: case m of Left x -> f x; Right _ -> pure () + rhs: whenLeft_ m f +- warn: + lhs: case m of Left x -> f x; Right _ -> return () + rhs: whenLeft_ m f +- warn: + lhs: case m of Left x -> f x; Right _ -> pass + rhs: whenLeft_ m f +- warn: + lhs: case m of Right _ -> pure () ; Left x -> f x + rhs: whenLeft_ m f +- warn: + lhs: case m of Right _ -> return (); Left x -> f x + rhs: whenLeft_ m f +- warn: + lhs: case m of Right _ -> pass ; Left x -> f x + rhs: whenLeft_ m f +- warn: + lhs: either f (\_ -> pure () ) m + rhs: whenLeft_ m f +- warn: + lhs: either f (\_ -> return () ) m + rhs: whenLeft_ m f +- warn: + lhs: either f (\_ -> pass ) m + rhs: whenLeft_ m f +- warn: + lhs: either f (const (pure () )) m + rhs: whenLeft_ m f +- warn: + lhs: either f (const (return ())) m + rhs: whenLeft_ m f +- warn: + lhs: either f (const pass) m + rhs: whenLeft_ m f +- warn: + lhs: m >>= \a -> whenLeft_ a f + rhs: whenLeftM_ m f +- warn: + lhs: m >>= \case Left x -> f x; Right _ -> pure () + rhs: whenLeftM_ m f +- warn: + lhs: m >>= \case Left x -> f x; Right _ -> return () + rhs: whenLeftM_ m f +- warn: + lhs: m >>= \case Left x -> f x; Right _ -> pass + rhs: whenLeftM_ m f +- warn: + lhs: m >>= \case Right _ -> pure () ; Left x -> f x + rhs: whenLeftM_ m f +- warn: + lhs: m >>= \case Right _ -> return (); Left x -> f x + rhs: whenLeftM_ m f +- warn: + lhs: m >>= \case Right _ -> pass ; Left x -> f x + rhs: whenLeftM_ m f +- warn: + lhs: either f (\_ -> pure () ) =<< m + rhs: whenLeftM_ m f +- warn: + lhs: either f (\_ -> return () ) =<< m + rhs: whenLeftM_ m f +- warn: + lhs: either f (\_ -> pass ) =<< m + rhs: whenLeftM_ m f +- warn: + lhs: either f (const (pure () )) =<< m + rhs: whenLeftM_ m f +- warn: + lhs: either f (const (return ())) =<< m + rhs: whenLeftM_ m f +- warn: + lhs: either f (const pass) =<< m + rhs: whenLeftM_ m f +- warn: + lhs: m >>= either f (\_ -> pure ()) + rhs: whenLeftM_ m f +- warn: + lhs: m >>= either f (\_ -> return ()) + rhs: whenLeftM_ m f +- warn: + lhs: m >>= either f (\_ -> pass) + rhs: whenLeftM_ m f +- warn: + lhs: m >>= either f (const (pure ()) ) + rhs: whenLeftM_ m f +- warn: + lhs: m >>= either f (const (return ())) + rhs: whenLeftM_ m f +- warn: + lhs: m >>= either f (const pass) + rhs: whenLeftM_ m f +- warn: + lhs: whenRight () + rhs: whenRight_ +- warn: + lhs: case m of Right x -> f x; Left _ -> pure () + rhs: whenRight_ m f +- warn: + lhs: case m of Right x -> f x; Left _ -> return () + rhs: whenRight_ m f +- warn: + lhs: case m of Right x -> f x; Left _ -> pass + rhs: whenRight_ m f +- warn: + lhs: case m of Left _ -> pure () ; Right x -> f x + rhs: whenRight_ m f +- warn: + lhs: case m of Left _ -> return (); Right x -> f x + rhs: whenRight_ m f +- warn: + lhs: case m of Left _ -> pass ; Right x -> f x + rhs: whenRight_ m f +- warn: + lhs: either (\_ -> pure () ) f m + rhs: whenRight_ m f +- warn: + lhs: either (\_ -> return () ) f m + rhs: whenRight_ m f +- warn: + lhs: either (\_ -> pass ) f m + rhs: whenRight_ m f +- warn: + lhs: either (const (pure () )) f m + rhs: whenRight_ m f +- warn: + lhs: either (const (return ())) f m + rhs: whenRight_ m f +- warn: + lhs: either (const pass) f m + rhs: whenRight_ m f +- warn: + lhs: m >>= \a -> whenRight_ a f + rhs: whenRightM_ m f +- warn: + lhs: 'm >>= \case Right x -> f x; Left _ -> pure () ' + rhs: whenRightM_ m f +- warn: + lhs: m >>= \case Right x -> f x; Left _ -> return () + rhs: whenRightM_ m f +- warn: + lhs: m >>= \case Right x -> f x; Left _ -> pass + rhs: whenRightM_ m f +- warn: + lhs: m >>= \case Left _ -> pure () ; Right x -> f x + rhs: whenRightM_ m f +- warn: + lhs: m >>= \case Left _ -> return (); Right x -> f x + rhs: whenRightM_ m f +- warn: + lhs: m >>= \case Left _ -> pass ; Right x -> f x + rhs: whenRightM_ m f +- warn: + lhs: either (\_ -> pure () ) f =<< m + rhs: whenRightM_ m f +- warn: + lhs: either (\_ -> return () ) f =<< m + rhs: whenRightM_ m f +- warn: + lhs: either (\_ -> pass ) f =<< m + rhs: whenRightM_ m f +- warn: + lhs: either (const (pure () )) f =<< m + rhs: whenRightM_ m f +- warn: + lhs: either (const (return ())) f =<< m + rhs: whenRightM_ m f +- warn: + lhs: either (const pass) f =<< m + rhs: whenRightM_ m f +- warn: + lhs: m >>= either (\_ -> pure ()) f + rhs: whenRightM_ m f +- warn: + lhs: m >>= either (\_ -> return ()) f + rhs: whenRightM_ m f +- warn: + lhs: m >>= either (\_ -> pass) f + rhs: whenRightM_ m f +- warn: + lhs: m >>= either (const (pure ()) ) f + rhs: whenRightM_ m f +- warn: + lhs: m >>= either (const (return ())) f + rhs: whenRightM_ m f +- warn: + lhs: m >>= either (const pass) f + rhs: whenRightM_ m f +- warn: + lhs: 'case m of Left x -> f x; Right _ -> pure d ' + rhs: whenLeft d m f +- warn: + lhs: case m of Left x -> f x; Right _ -> return d + rhs: whenLeft d m f +- warn: + lhs: case m of Right _ -> pure d ; Left x -> f x + rhs: whenLeft d m f +- warn: + lhs: case m of Right _ -> return d; Left x -> f x + rhs: whenLeft d m f +- warn: + lhs: either f (\_ -> pure d ) m + rhs: whenLeft d m f +- warn: + lhs: either f (\_ -> return d ) m + rhs: whenLeft d m f +- warn: + lhs: either f (const (pure d )) m + rhs: whenLeft d m f +- warn: + lhs: either f (const (return d)) m + rhs: whenLeft d m f +- warn: + lhs: m >>= \a -> whenLeft d a f + rhs: whenLeftM d m f +- warn: + lhs: m >>= \case Left x -> f x; Right _ -> pure d + rhs: whenLeftM d m f +- warn: + lhs: m >>= \case Left x -> f x; Right _ -> return d + rhs: whenLeftM d m f +- warn: + lhs: m >>= \case Right _ -> pure d ; Left x -> f x + rhs: whenLeftM d m f +- warn: + lhs: m >>= \case Right _ -> return d; Left x -> f x + rhs: whenLeftM d m f +- warn: + lhs: either f (\_ -> pure d ) =<< m + rhs: whenLeftM d m f +- warn: + lhs: either f (\_ -> return d ) =<< m + rhs: whenLeftM d m f +- warn: + lhs: either f (const (pure d )) =<< m + rhs: whenLeftM d m f +- warn: + lhs: either f (const (return d)) =<< m + rhs: whenLeftM d m f +- warn: + lhs: m >>= either f (\_ -> pure d) + rhs: whenLeftM d m f +- warn: + lhs: m >>= either f (\_ -> return d) + rhs: whenLeftM d m f +- warn: + lhs: m >>= either f (const (pure d)) + rhs: whenLeftM d m f +- warn: + lhs: m >>= either f (const (return d)) + rhs: whenLeftM d m f +- warn: + lhs: case m of Right x -> f x; Left _ -> pure d + rhs: whenRight d m f +- warn: + lhs: case m of Right x -> f x; Left _ -> return d + rhs: whenRight d m f +- warn: + lhs: case m of Left _ -> pure d ; Right x -> f x + rhs: whenRight d m f +- warn: + lhs: case m of Left _ -> return d; Right x -> f x + rhs: whenRight d m f +- warn: + lhs: either (\_ -> pure d ) f m + rhs: whenRight d m f +- warn: + lhs: either (\_ -> return d ) f m + rhs: whenRight d m f +- warn: + lhs: either (const (pure d )) f m + rhs: whenRight d m f +- warn: + lhs: either (const (return d)) f m + rhs: whenRight d m f +- warn: + lhs: m >>= \a -> whenRight d a f + rhs: whenRightM d m f +- warn: + lhs: m >>= \case Right x -> f x; Left _ -> pure d + rhs: whenRightM d m f +- warn: + lhs: m >>= \case Right x -> f x; Left _ -> return d + rhs: whenRightM d m f +- warn: + lhs: m >>= \case Left _ -> pure d ; Right x -> f x + rhs: whenRightM d m f +- warn: + lhs: m >>= \case Left _ -> return d; Right x -> f x + rhs: whenRightM d m f +- warn: + lhs: either (\_ -> pure d ) f =<< m + rhs: whenRightM d m f +- warn: + lhs: either (\_ -> return d ) f =<< m + rhs: whenRightM d m f +- warn: + lhs: either (const (pure d )) f =<< m + rhs: whenRightM d m f +- warn: + lhs: either (const (return d)) f =<< m + rhs: whenRightM d m f +- warn: + lhs: m >>= either (\_ -> pure d) f + rhs: whenRightM d m f +- warn: + lhs: m >>= either (\_ -> return d) f + rhs: whenRightM d m f +- warn: + lhs: m >>= either (const (pure d) ) f + rhs: whenRightM d m f +- warn: + lhs: m >>= either (const (return d)) f + rhs: whenRightM d m f +- warn: + lhs: case m of [] -> return (); (x:xs) -> f (x :| xs) + rhs: whenNotNull m f +- warn: + lhs: case m of [] -> pure () ; (x:xs) -> f (x :| xs) + rhs: whenNotNull m f +- warn: + lhs: case m of [] -> pass ; (x:xs) -> f (x :| xs) + rhs: whenNotNull m f +- warn: + lhs: case m of (x:xs) -> f (x :| xs); [] -> return () + rhs: whenNotNull m f +- warn: + lhs: 'case m of (x:xs) -> f (x :| xs); [] -> pure () ' + rhs: whenNotNull m f +- warn: + lhs: 'case m of (x:xs) -> f (x :| xs); [] -> pass ' + rhs: whenNotNull m f +- warn: + lhs: m >>= \case [] -> pass ; (x:xs) -> f (x :| xs) + rhs: whenNotNullM m f +- warn: + lhs: m >>= \case [] -> pure () ; (x:xs) -> f (x :| xs) + rhs: whenNotNullM m f +- warn: + lhs: m >>= \case [] -> return (); (x:xs) -> f (x :| xs) + rhs: whenNotNullM m f +- warn: + lhs: 'm >>= \case (x:xs) -> f (x :| xs); [] -> pass ' + rhs: whenNotNullM m f +- warn: + lhs: 'm >>= \case (x:xs) -> f (x :| xs); [] -> pure () ' + rhs: whenNotNullM m f +- warn: + lhs: m >>= \case (x:xs) -> f (x :| xs); [] -> return () + rhs: whenNotNullM m f +- warn: + lhs: mapMaybe leftToMaybe + rhs: lefts +- warn: + lhs: mapMaybe rightToMaybe + rhs: rights +- warn: + lhs: flip runReaderT + rhs: usingReaderT +- warn: + lhs: flip runReader + rhs: usingReader +- warn: + lhs: flip runStateT + rhs: usingStateT +- warn: + lhs: flip runState + rhs: usingState +- warn: + lhs: fst <$> usingStateT s st + rhs: evaluatingStateT s st +- warn: + lhs: fst (usingState s st) + rhs: evaluatingState s st +- warn: + lhs: snd <$> usingStateT s st + rhs: executingStateT s st +- warn: + lhs: snd (usingState s st) + rhs: executingState s st +- warn: + lhs: MaybeT (pure m) + rhs: hoistMaybe m +- warn: + lhs: MaybeT (return m) + rhs: hoistMaybe m +- warn: + lhs: MaybeT . pure + rhs: hoistMaybe +- warn: + lhs: MaybeT . return + rhs: hoistMaybe +- warn: + lhs: ExceptT (pure m) + rhs: hoistEither m +- warn: + lhs: ExceptT (return m) + rhs: hoistEither m +- warn: + lhs: ExceptT . pure + rhs: hoistEither +- warn: + lhs: ExceptT . return + rhs: hoistEither +- warn: + lhs: fromMaybe mempty + rhs: maybeToMonoid +- warn: + lhs: 'm ?: mempty' + rhs: maybeToMonoid m +- warn: + lhs: Data.Map.toAscList (Data.Map.fromList x) + rhs: sortWith fst x +- warn: + lhs: Data.Map.toDescList (Data.Map.fromList x) + rhs: sortWith (Down . fst) x +- warn: + lhs: Data.Set.toList (Data.Set.fromList l) + rhs: sortNub l +- warn: + lhs: Data.Set.assocs (Data.Set.fromList l) + rhs: sortNub l +- warn: + lhs: Data.Set.toAscList (Data.Set.fromList l) + rhs: sortNub l +- warn: + lhs: Data.HashSet.toList (Data.HashSet.fromList l) + rhs: unstableNub l +- warn: + lhs: nub + note: '''nub'' is O(n^2), ''ordNub'' is O(n log n)' + rhs: ordNub +- warn: + lhs: sortBy (comparing 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. + rhs: sortWith f +- warn: + lhs: sortOn fst + note: '''sortWith'' will be faster here because it doesn''t do caching' + rhs: sortWith fst +- warn: + lhs: sortOn snd + note: '''sortWith'' will be faster here because it doesn''t do caching' + rhs: sortWith snd +- warn: + lhs: sortOn (Down . fst) + note: '''sortWith'' will be faster here because it doesn''t do caching' + rhs: sortWith (Down . fst) +- warn: + lhs: sortOn (Down . snd) + note: '''sortWith'' will be faster here because it doesn''t do caching' + rhs: sortWith (Down . snd) +- warn: + lhs: Data.Text.IO.putStr + rhs: putText +- warn: + lhs: Data.Text.IO.putStrLn + rhs: putTextLn +- warn: + lhs: Data.Text.Lazy.IO.putStr + rhs: putLText +- warn: + lhs: Data.Text.Lazy.IO.putStrLn + rhs: putLTextLn +- warn: + lhs: Data.ByteString.Char8.putStr + rhs: putBS +- warn: + lhs: Data.ByteString.Char8.putStrLn + rhs: putBSLn +- warn: + lhs: Data.ByteString.Lazy.Char8.putStr + rhs: putLBS +- warn: + lhs: Data.ByteString.Lazy.Char8.putStrLn + rhs: putLBSLn +- warn: + lhs: Data.Text.Lazy.Text + rhs: LText +- warn: + lhs: Data.ByteString.Lazy.ByteString + rhs: LByteString +- warn: + lhs: Data.ByteString.UTF8.fromString + rhs: encodeUtf8 +- warn: + lhs: Data.ByteString.UTF8.toString + rhs: decodeUtf8 +- warn: + lhs: Data.Text.Encoding.encodeUtf8 + rhs: encodeUtf8 +- warn: + lhs: Data.Text.Encoding.decodeUtf8 + rhs: decodeUtf8 +- warn: + lhs: Data.ByteString.Lazy.toStrict (encodeUtf8 x) + rhs: encodeUtf8 x +- warn: + lhs: toStrict (encodeUtf8 x) + rhs: encodeUtf8 x +- warn: + lhs: decodeUtf8 (Data.ByteString.Lazy.fromStrict x) + rhs: decodeUtf8 x +- warn: + lhs: decodeUtf8 (fromStrict x) + rhs: decodeUtf8 x +- warn: + lhs: Data.ByteString.Lazy.UTF8.fromString + rhs: encodeUtf8 +- warn: + lhs: Data.ByteString.Lazy.UTF8.toString + rhs: decodeUtf8 +- warn: + lhs: Data.ByteString.Lazy.fromStrict (Data.Text.Encoding.encodeUtf8 x) + rhs: encodeUtf8 x +- warn: + lhs: Data.ByteString.Lazy.fromStrict (encodeUtf8 x) + rhs: encodeUtf8 x +- warn: + lhs: Data.Text.Encoding.decodeUtf8 (Data.ByteString.Lazy.toStrict x) + rhs: decodeUtf8 x +- warn: + lhs: Data.Text.Encoding.decodeUtf8 (toStrict x) + rhs: decodeUtf8 x +- warn: + lhs: decodeUtf8 (Data.ByteString.Lazy.toStrict x) + rhs: decodeUtf8 x +- warn: + lhs: decodeUtf8 (toStrict x) + rhs: decodeUtf8 x +- warn: + lhs: Data.Text.pack + rhs: toText +- warn: + lhs: Data.Text.unpack + rhs: toString +- warn: + lhs: Data.Text.Lazy.pack + rhs: toLText +- warn: + lhs: Data.Text.Lazy.unpack + rhs: toString +- warn: + lhs: Data.Text.Lazy.toStrict + rhs: toText +- warn: + lhs: Data.Text.Lazy.fromStrict + rhs: toLText +- warn: + lhs: Data.Text.pack (show x) + rhs: show x +- warn: + lhs: Data.Text.Lazy.pack (show x) + rhs: show x +- warn: + lhs: Data.ByteString.Lazy.fromStrict + rhs: fromStrict +- warn: + lhs: Data.ByteString.Lazy.toStrict + rhs: toStrict +- warn: + lhs: Data.Text.Lazy.fromStrict + rhs: fromStrict +- warn: + lhs: Data.Text.Lazy.toStrict + rhs: toStrict +- warn: + lhs: Control.Applicative.Alternative + note: '''Alternative'' is already exported from Relude' + name: Use 'Alternative' from Relude + rhs: Alternative +- warn: + lhs: Control.Applicative.empty + note: '''empty'' is already exported from Relude' + name: Use 'empty' from Relude + rhs: empty +- warn: + lhs: (Control.Applicative.<|>) + note: Operator '(<|>)' is already exported from Relude + name: Use '<|>' from Relude + rhs: (<|>) +- warn: + lhs: Control.Applicative.some + note: '''some'' is already exported from Relude' + name: Use 'some' from Relude + rhs: some +- warn: + lhs: Control.Applicative.many + note: '''many'' is already exported from Relude' + name: Use 'many' from Relude + rhs: many +- warn: + lhs: Control.Applicative.Const + note: '''Const'' is already exported from Relude' + name: Use 'Const' from Relude + rhs: Const +- warn: + lhs: Control.Applicative.getConst + note: '''getConst'' is already exported from Relude' + name: Use 'getConst' from Relude + rhs: getConst +- warn: + lhs: Control.Applicative.ZipList + note: '''ZipList'' is already exported from Relude' + name: Use 'ZipList' from Relude + rhs: ZipList +- warn: + lhs: Control.Applicative.getZipList + note: '''getZipList'' is already exported from Relude' + name: Use 'getZipList' from Relude + rhs: getZipList +- warn: + lhs: Control.Applicative.liftA2 + note: '''liftA2'' is already exported from Relude' + name: Use 'liftA2' from Relude + rhs: liftA2 +- warn: + lhs: Control.Applicative.liftA3 + note: '''liftA3'' is already exported from Relude' + name: Use 'liftA3' from Relude + rhs: liftA3 +- warn: + lhs: Control.Applicative.optional + note: '''optional'' is already exported from Relude' + name: Use 'optional' from Relude + rhs: optional +- warn: + lhs: (Control.Applicative.<**>) + note: Operator '(<**>)' is already exported from Relude + name: Use '<**>' from Relude + rhs: (<**>) +- warn: + lhs: Data.Bits.xor + note: '''xor'' is already exported from Relude' + name: Use 'xor' from Relude + rhs: xor +- warn: + lhs: Data.Char.chr + note: '''chr'' is already exported from Relude' + name: Use 'chr' from Relude + rhs: chr +- warn: + lhs: Data.Int.Int8 + note: '''Int8'' is already exported from Relude' + name: Use 'Int8' from Relude + rhs: Int8 +- warn: + lhs: Data.Int.Int16 + note: '''Int16'' is already exported from Relude' + name: Use 'Int16' from Relude + rhs: Int16 +- warn: + lhs: Data.Int.Int32 + note: '''Int32'' is already exported from Relude' + name: Use 'Int32' from Relude + rhs: Int32 +- warn: + lhs: Data.Int.Int64 + note: '''Int64'' is already exported from Relude' + name: Use 'Int64' from Relude + rhs: Int64 +- warn: + lhs: Data.Word.Word8 + note: '''Word8'' is already exported from Relude' + name: Use 'Word8' from Relude + rhs: Word8 +- warn: + lhs: Data.Word.Word16 + note: '''Word16'' is already exported from Relude' + name: Use 'Word16' from Relude + rhs: Word16 +- warn: + lhs: Data.Word.Word32 + note: '''Word32'' is already exported from Relude' + name: Use 'Word32' from Relude + rhs: Word32 +- warn: + lhs: Data.Word.Word64 + note: '''Word64'' is already exported from Relude' + name: Use 'Word64' from Relude + rhs: Word64 +- warn: + lhs: Data.Word.byteSwap16 + note: '''byteSwap16'' is already exported from Relude' + name: Use 'byteSwap16' from Relude + rhs: byteSwap16 +- warn: + lhs: Data.Word.byteSwap32 + note: '''byteSwap32'' is already exported from Relude' + name: Use 'byteSwap32' from Relude + rhs: byteSwap32 +- warn: + lhs: Data.Word.byteSwap64 + note: '''byteSwap64'' is already exported from Relude' + name: Use 'byteSwap64' from Relude + rhs: byteSwap64 +- warn: + lhs: Numeric.Natural.Natural + note: '''Natural'' is already exported from Relude' + name: Use 'Natural' from Relude + rhs: Natural +- warn: + lhs: System.IO.Handle + note: '''Handle'' is already exported from Relude' + name: Use 'Handle' from Relude + rhs: Handle +- warn: + lhs: System.IO.IOMode + note: '''IOMode'' is already exported from Relude' + name: Use 'IOMode' from Relude + rhs: IOMode +- warn: + lhs: System.IO.ReadMode + note: '''ReadMode'' is already exported from Relude' + name: Use 'ReadMode' from Relude + rhs: ReadMode +- warn: + lhs: System.IO.WriteMode + note: '''WriteMode'' is already exported from Relude' + name: Use 'WriteMode' from Relude + rhs: WriteMode +- warn: + lhs: System.IO.AppendMode + note: '''AppendMode'' is already exported from Relude' + name: Use 'AppendMode' from Relude + rhs: AppendMode +- warn: + lhs: System.IO.ReadWriteMode + note: '''ReadWriteMode'' is already exported from Relude' + name: Use 'ReadWriteMode' from Relude + rhs: ReadWriteMode +- warn: + lhs: System.IO.stderr + note: '''stderr'' is already exported from Relude' + name: Use 'stderr' from Relude + rhs: stderr +- warn: + lhs: System.IO.stdin + note: '''stdin'' is already exported from Relude' + name: Use 'stdin' from Relude + rhs: stdin +- warn: + lhs: System.IO.stdout + note: '''stdout'' is already exported from Relude' + name: Use 'stdout' from Relude + rhs: stdout +- warn: + lhs: System.IO.withFile + note: '''withFile'' is already exported from Relude' + name: Use 'withFile' from Relude + rhs: withFile +- warn: + lhs: Data.Ord.Down + note: '''Down'' is already exported from Relude' + name: Use 'Down' from Relude + rhs: Down +- warn: + lhs: Data.Ord.comparing + note: '''comparing'' is already exported from Relude' + name: Use 'comparing' from Relude + rhs: comparing +- warn: + lhs: Data.Coerce.Coercible + note: '''Coercible'' is already exported from Relude' + name: Use 'Coercible' from Relude + rhs: Coercible +- warn: + lhs: Data.Coerce.coerce + note: '''coerce'' is already exported from Relude' + name: Use 'coerce' from Relude + rhs: coerce +- warn: + lhs: Data.Kind.Constraint + note: '''Constraint'' is already exported from Relude' + name: Use 'Constraint' from Relude + rhs: Constraint +- warn: + lhs: Data.Kind.Type + note: '''Type'' is already exported from Relude' + name: Use 'Type' from Relude + rhs: Type +- warn: + lhs: Data.Typeable.Typeable + note: '''Typeable'' is already exported from Relude' + name: Use 'Typeable' from Relude + rhs: Typeable +- warn: + lhs: Data.Proxy.Proxy + note: '''Proxy'' is already exported from Relude' + name: Use 'Proxy' from Relude + rhs: Proxy +- warn: + lhs: Data.Typeable.Typeable + note: '''Typeable'' is already exported from Relude' + name: Use 'Typeable' from Relude + rhs: Typeable +- warn: + lhs: Data.Void.Void + note: '''Void'' is already exported from Relude' + name: Use 'Void' from Relude + rhs: Void +- warn: + lhs: Data.Void.absurd + note: '''absurd'' is already exported from Relude' + name: Use 'absurd' from Relude + rhs: absurd +- warn: + lhs: Data.Void.vacuous + note: '''vacuous'' is already exported from Relude' + name: Use 'vacuous' from Relude + rhs: vacuous +- warn: + lhs: Data.Base.maxInt + note: '''maxInt'' is already exported from Relude' + name: Use 'maxInt' from Relude + rhs: maxInt +- warn: + lhs: Data.Base.minInt + note: '''minInt'' is already exported from Relude' + name: Use 'minInt' from Relude + rhs: minInt +- warn: + lhs: Data.Base.ord + note: '''ord'' is already exported from Relude' + name: Use 'ord' from Relude + rhs: ord +- warn: + lhs: GHC.Enum.boundedEnumFrom + note: '''boundedEnumFrom'' is already exported from Relude' + name: Use 'boundedEnumFrom' from Relude + rhs: boundedEnumFrom +- warn: + lhs: GHC.Enum.boundedEnumFromThen + note: '''boundedEnumFromThen'' is already exported from Relude' + name: Use 'boundedEnumFromThen' from Relude + rhs: boundedEnumFromThen +- warn: + lhs: GHC.Generics.Generic + note: '''Generic'' is already exported from Relude' + name: Use 'Generic' from Relude + rhs: Generic +- warn: + lhs: GHC.Real.Ratio + note: '''Ratio'' is already exported from Relude' + name: Use 'Ratio' from Relude + rhs: Ratio +- warn: + lhs: GHC.Real.Rational + note: '''Rational'' is already exported from Relude' + name: Use 'Rational' from Relude + rhs: Rational +- warn: + lhs: GHC.Real.denominator + note: '''denominator'' is already exported from Relude' + name: Use 'denominator' from Relude + rhs: denominator +- warn: + lhs: GHC.Real.numerator + note: '''numerator'' is already exported from Relude' + name: Use 'numerator' from Relude + rhs: numerator +- warn: + lhs: GHC.TypeNats.CmpNat + note: '''CmpNat'' is already exported from Relude' + name: Use 'CmpNat' from Relude + rhs: CmpNat +- warn: + lhs: GHC.TypeNats.KnownNat + note: '''KnownNat'' is already exported from Relude' + name: Use 'KnownNat' from Relude + rhs: KnownNat +- warn: + lhs: GHC.TypeNats.Nat + note: '''Nat'' is already exported from Relude' + name: Use 'Nat' from Relude + rhs: Nat +- warn: + lhs: GHC.TypeNats.SomeNat + note: '''SomeNat'' is already exported from Relude' + name: Use 'SomeNat' from Relude + rhs: SomeNat +- warn: + lhs: GHC.TypeNats.natVal + note: '''natVal'' is already exported from Relude' + name: Use 'natVal' from Relude + rhs: natVal +- warn: + lhs: GHC.TypeNats.someNatVal + note: '''someNatVal'' is already exported from Relude' + name: Use 'someNatVal' from Relude + rhs: someNatVal +- warn: + lhs: GHC.TypeLits.CmpNat + note: '''CmpNat'' is already exported from Relude' + name: Use 'CmpNat' from Relude + rhs: CmpNat +- warn: + lhs: GHC.TypeLits.KnownNat + note: '''KnownNat'' is already exported from Relude' + name: Use 'KnownNat' from Relude + rhs: KnownNat +- warn: + lhs: GHC.TypeLits.Nat + note: '''Nat'' is already exported from Relude' + name: Use 'Nat' from Relude + rhs: Nat +- warn: + lhs: GHC.TypeLits.SomeNat + note: '''SomeNat'' is already exported from Relude' + name: Use 'SomeNat' from Relude + rhs: SomeNat +- warn: + lhs: GHC.TypeLits.natVal + note: '''natVal'' is already exported from Relude' + name: Use 'natVal' from Relude + rhs: natVal +- warn: + lhs: GHC.TypeLits.someNatVal + note: '''someNatVal'' is already exported from Relude' + name: Use 'someNatVal' from Relude + rhs: someNatVal +- warn: + lhs: GHC.ExecutionStack.getStackTrace + note: '''getStackTrace'' is already exported from Relude' + name: Use 'getStackTrace' from Relude + rhs: getStackTrace +- warn: + lhs: GHC.ExecutionStack.showStackTrace + note: '''showStackTrace'' is already exported from Relude' + name: Use 'showStackTrace' from Relude + rhs: showStackTrace +- warn: + lhs: GHC.OverloadedLabels.IsLabel + note: '''IsLabel'' is already exported from Relude' + name: Use 'IsLabel' from Relude + rhs: IsLabel +- warn: + lhs: GHC.OverloadedLabels.fromLabel + note: '''fromLabel'' is already exported from Relude' + name: Use 'fromLabel' from Relude + rhs: fromLabel +- warn: + lhs: GHC.Stack.CallStack + note: '''CallStack'' is already exported from Relude' + name: Use 'CallStack' from Relude + rhs: CallStack +- warn: + lhs: GHC.Stack.HasCallStack + note: '''HasCallStack'' is already exported from Relude' + name: Use 'HasCallStack' from Relude + rhs: HasCallStack +- warn: + lhs: GHC.Stack.callStack + note: '''callStack'' is already exported from Relude' + name: Use 'callStack' from Relude + rhs: callStack +- warn: + lhs: GHC.Stack.currentCallStack + note: '''currentCallStack'' is already exported from Relude' + name: Use 'currentCallStack' from Relude + rhs: currentCallStack +- warn: + lhs: GHC.Stack.getCallStack + note: '''getCallStack'' is already exported from Relude' + name: Use 'getCallStack' from Relude + rhs: getCallStack +- warn: + lhs: GHC.Stack.prettyCallStack + note: '''prettyCallStack'' is already exported from Relude' + name: Use 'prettyCallStack' from Relude + rhs: prettyCallStack +- warn: + lhs: GHC.Stack.prettySrcLoc + note: '''prettySrcLoc'' is already exported from Relude' + name: Use 'prettySrcLoc' from Relude + rhs: prettySrcLoc +- warn: + lhs: GHC.Stack.withFrozenCallStack + note: '''withFrozenCallStack'' is already exported from Relude' + name: Use 'withFrozenCallStack' from Relude + rhs: withFrozenCallStack +- warn: + lhs: Data.Bifoldable.Bifoldable + note: '''Bifoldable'' is already exported from Relude' + name: Use 'Bifoldable' from Relude + rhs: Bifoldable +- warn: + lhs: Data.Bifoldable.bifold + note: '''bifold'' is already exported from Relude' + name: Use 'bifold' from Relude + rhs: bifold +- warn: + lhs: Data.Bifoldable.bifoldMap + note: '''bifoldMap'' is already exported from Relude' + name: Use 'bifoldMap' from Relude + rhs: bifoldMap +- warn: + lhs: Data.Bifoldable.bifoldr + note: '''bifoldr'' is already exported from Relude' + name: Use 'bifoldr' from Relude + rhs: bifoldr +- warn: + lhs: Data.Bifoldable.bifoldl + note: '''bifoldl'' is already exported from Relude' + name: Use 'bifoldl' from Relude + rhs: bifoldl +- warn: + lhs: Data.Bifoldable.bifoldl' + note: '''bifoldl'''' is already exported from Relude' + name: Use 'bifoldl'' from Relude + rhs: bifoldl' +- warn: + lhs: Data.Bifoldable.bifoldlM + note: '''bifoldlM'' is already exported from Relude' + name: Use 'bifoldlM' from Relude + rhs: bifoldlM +- warn: + lhs: Data.Bifoldable.bifoldr' + note: '''bifoldr'''' is already exported from Relude' + name: Use 'bifoldr'' from Relude + rhs: bifoldr' +- warn: + lhs: Data.Bifoldable.bifoldrM + note: '''bifoldrM'' is already exported from Relude' + name: Use 'bifoldrM' from Relude + rhs: bifoldrM +- warn: + lhs: Data.Bifoldable.bitraverse_ + note: '''bitraverse_'' is already exported from Relude' + name: Use 'bitraverse_' from Relude + rhs: bitraverse_ +- warn: + lhs: Data.Bifoldable.bifor_ + note: '''bifor_'' is already exported from Relude' + name: Use 'bifor_' from Relude + rhs: bifor_ +- warn: + lhs: Data.Bifoldable.biasum + note: '''biasum'' is already exported from Relude' + name: Use 'biasum' from Relude + rhs: biasum +- warn: + lhs: Data.Bifoldable.bisequence_ + note: '''bisequence_'' is already exported from Relude' + name: Use 'bisequence_' from Relude + rhs: bisequence_ +- warn: + lhs: Data.Bifoldable.biList + note: '''biList'' is already exported from Relude' + name: Use 'biList' from Relude + rhs: biList +- warn: + lhs: Data.Bifoldable.binull + note: '''binull'' is already exported from Relude' + name: Use 'binull' from Relude + rhs: binull +- warn: + lhs: Data.Bifoldable.bilength + note: '''bilength'' is already exported from Relude' + name: Use 'bilength' from Relude + rhs: bilength +- warn: + lhs: Data.Bifoldable.bielem + note: '''bielem'' is already exported from Relude' + name: Use 'bielem' from Relude + rhs: bielem +- warn: + lhs: Data.Bifoldable.biand + note: '''biand'' is already exported from Relude' + name: Use 'biand' from Relude + rhs: biand +- warn: + lhs: Data.Bifoldable.bior + note: '''bior'' is already exported from Relude' + name: Use 'bior' from Relude + rhs: bior +- warn: + lhs: Data.Bifoldable.biany + note: '''biany'' is already exported from Relude' + name: Use 'biany' from Relude + rhs: biany +- warn: + lhs: Data.Bifoldable.biall + note: '''biall'' is already exported from Relude' + name: Use 'biall' from Relude + rhs: biall +- warn: + lhs: Data.Bifoldable.bifind + note: '''bifind'' is already exported from Relude' + name: Use 'bifind' from Relude + rhs: bifind +- warn: + lhs: Data.Bitraversable.Bitraversable + note: '''Bitraversable'' is already exported from Relude' + name: Use 'Bitraversable' from Relude + rhs: Bitraversable +- warn: + lhs: Data.Bitraversable.bitraverse + note: '''bitraverse'' is already exported from Relude' + name: Use 'bitraverse' from Relude + rhs: bitraverse +- warn: + lhs: Data.Bitraversable.bisequence + note: '''bisequence'' is already exported from Relude' + name: Use 'bisequence' from Relude + rhs: bisequence +- warn: + lhs: Data.Bitraversable.bifor + note: '''bifor'' is already exported from Relude' + name: Use 'bifor' from Relude + rhs: bifor +- warn: + lhs: Data.Bitraversable.bimapDefault + note: '''bimapDefault'' is already exported from Relude' + name: Use 'bimapDefault' from Relude + rhs: bimapDefault +- warn: + lhs: Data.Bitraversable.bifoldMapDefault + note: '''bifoldMapDefault'' is already exported from Relude' + name: Use 'bifoldMapDefault' from Relude + rhs: bifoldMapDefault +- warn: + lhs: Control.Monad.guard + note: '''guard'' is already exported from Relude' + name: Use 'guard' from Relude + rhs: guard +- warn: + lhs: Control.Monad.unless + note: '''unless'' is already exported from Relude' + name: Use 'unless' from Relude + rhs: unless +- warn: + lhs: Control.Monad.when + note: '''when'' is already exported from Relude' + name: Use 'when' from Relude + rhs: when +- warn: + lhs: Data.Bool.bool + note: '''bool'' is already exported from Relude' + name: Use 'bool' from Relude + rhs: bool +- warn: + lhs: Data.Hashable.Hashable + note: '''Hashable'' is already exported from Relude' + name: Use 'Hashable' from Relude + rhs: Hashable +- warn: + lhs: Data.Hashable.hashWithSalt + note: '''hashWithSalt'' is already exported from Relude' + name: Use 'hashWithSalt' from Relude + rhs: hashWithSalt +- warn: + lhs: Data.HashMap.Strict.HashMap + note: '''HashMap'' is already exported from Relude' + name: Use 'HashMap' from Relude + rhs: HashMap +- warn: + lhs: Data.HashSet.HashSet + note: '''HashSet'' is already exported from Relude' + name: Use 'HashSet' from Relude + rhs: HashSet +- warn: + lhs: Data.IntMap.Strict.IntMap + note: '''IntMap'' is already exported from Relude' + name: Use 'IntMap' from Relude + rhs: IntMap +- warn: + lhs: Data.IntSet.IntSet + note: '''IntSet'' is already exported from Relude' + name: Use 'IntSet' from Relude + rhs: IntSet +- warn: + lhs: Data.Map.Strict.Map + note: '''Map'' is already exported from Relude' + name: Use 'Map' from Relude + rhs: Map +- warn: + lhs: Data.Sequence.Sequence + note: '''Sequence'' is already exported from Relude' + name: Use 'Sequence' from Relude + rhs: Sequence +- warn: + lhs: Data.Set.Set + note: '''Set'' is already exported from Relude' + name: Use 'Set' from Relude + rhs: Set +- warn: + lhs: Data.Tuple.swap + note: '''swap'' is already exported from Relude' + name: Use 'swap' from Relude + rhs: swap +- warn: + lhs: Data.Vector.Vector + note: '''Vector'' is already exported from Relude' + name: Use 'Vector' from Relude + rhs: Vector +- warn: + lhs: GHC.Exts.IsList + note: '''IsList'' is already exported from Relude' + name: Use 'IsList' from Relude + rhs: IsList +- warn: + lhs: GHC.Exts.fromList + note: '''fromList'' is already exported from Relude' + name: Use 'fromList' from Relude + rhs: fromList +- warn: + lhs: GHC.Exts.fromListN + note: '''fromListN'' is already exported from Relude' + name: Use 'fromListN' from Relude + rhs: fromListN +- warn: + lhs: Debug.Trace.trace + note: '''trace'' is already exported from Relude' + name: Use 'trace' from Relude + rhs: trace +- warn: + lhs: Debug.Trace.traceShow + note: '''traceShow'' is already exported from Relude' + name: Use 'traceShow' from Relude + rhs: traceShow +- warn: + lhs: Debug.Trace.traceShowId + note: '''traceShowId'' is already exported from Relude' + name: Use 'traceShowId' from Relude + rhs: traceShowId +- warn: + lhs: Debug.Trace.traceShowM + note: '''traceShowM'' is already exported from Relude' + name: Use 'traceShowM' from Relude + rhs: traceShowM +- warn: + lhs: Debug.Trace.traceM + note: '''traceM'' is already exported from Relude' + name: Use 'traceM' from Relude + rhs: traceM +- warn: + lhs: Debug.Trace.traceId + note: '''traceId'' is already exported from Relude' + name: Use 'traceId' from Relude + rhs: traceId +- warn: + lhs: Control.DeepSeq.NFData + note: '''NFData'' is already exported from Relude' + name: Use 'NFData' from Relude + rhs: NFData +- warn: + lhs: Control.DeepSeq.rnf + note: '''rnf'' is already exported from Relude' + name: Use 'rnf' from Relude + rhs: rnf +- warn: + lhs: Control.DeepSeq.deepseq + note: '''deepseq'' is already exported from Relude' + name: Use 'deepseq' from Relude + rhs: deepseq +- warn: + lhs: Control.DeepSeq.force + note: '''force'' is already exported from Relude' + name: Use 'force' from Relude + rhs: force +- warn: + lhs: (Control.DeepSeq.$!!) + note: Operator '($!!)' is already exported from Relude + name: Use '$!!' from Relude + rhs: ($!!) +- warn: + lhs: Control.Exception.Exception + note: '''Exception'' is already exported from Relude' + name: Use 'Exception' from Relude + rhs: Exception +- warn: + lhs: Control.Exception.SomeException + note: '''SomeException'' is already exported from Relude' + name: Use 'SomeException' from Relude + rhs: SomeException +- warn: + lhs: Control.Exception.toException + note: '''toException'' is already exported from Relude' + name: Use 'toException' from Relude + rhs: toException +- warn: + lhs: Control.Exception.fromException + note: '''fromException'' is already exported from Relude' + name: Use 'fromException' from Relude + rhs: fromException +- warn: + lhs: Control.Exception.displayException + note: '''displayException'' is already exported from Relude' + name: Use 'displayException' from Relude + rhs: displayException +- warn: + lhs: Data.Foldable.asum + note: '''asum'' is already exported from Relude' + name: Use 'asum' from Relude + rhs: asum +- warn: + lhs: Data.Foldable.find + note: '''find'' is already exported from Relude' + name: Use 'find' from Relude + rhs: find +- warn: + lhs: Data.Foldable.find + note: '''find'' is already exported from Relude' + name: Use 'find' from Relude + rhs: find +- warn: + lhs: Data.Foldable.fold + note: '''fold'' is already exported from Relude' + name: Use 'fold' from Relude + rhs: fold +- warn: + lhs: Data.Foldable.foldl' + note: '''foldl'''' is already exported from Relude' + name: Use 'foldl'' from Relude + rhs: foldl' +- warn: + lhs: Data.Foldable.foldrM + note: '''foldrM'' is already exported from Relude' + name: Use 'foldrM' from Relude + rhs: foldrM +- warn: + lhs: Data.Foldable.forM_ + note: '''forM_'' is already exported from Relude' + name: Use 'forM_' from Relude + rhs: forM_ +- warn: + lhs: Data.Foldable.for_ + note: '''for_'' is already exported from Relude' + name: Use 'for_' from Relude + rhs: for_ +- warn: + lhs: Data.Foldable.sequenceA_ + note: '''sequenceA_'' is already exported from Relude' + name: Use 'sequenceA_' from Relude + rhs: sequenceA_ +- warn: + lhs: Data.Foldable.toList + note: '''toList'' is already exported from Relude' + name: Use 'toList' from Relude + rhs: toList +- warn: + lhs: Data.Foldable.traverse_ + note: '''traverse_'' is already exported from Relude' + name: Use 'traverse_' from Relude + rhs: traverse_ +- warn: + lhs: Data.Traversable.forM + note: '''forM'' is already exported from Relude' + name: Use 'forM' from Relude + rhs: forM +- warn: + lhs: Data.Traversable.mapAccumL + note: '''mapAccumL'' is already exported from Relude' + name: Use 'mapAccumL' from Relude + rhs: mapAccumL +- warn: + lhs: Data.Traversable.mapAccumR + note: '''mapAccumR'' is already exported from Relude' + name: Use 'mapAccumR' from Relude + rhs: mapAccumR +- warn: + lhs: (Control.Arrow.&&&) + note: Operator '(&&&)' is already exported from Relude + name: Use '&&&' from Relude + rhs: (&&&) +- warn: + lhs: (Control.Category.>>>) + note: Operator '(>>>)' is already exported from Relude + name: Use '>>>' from Relude + rhs: (>>>) +- warn: + lhs: (Control.Category.<<<) + note: Operator '(<<<)' is already exported from Relude + name: Use '<<<' from Relude + rhs: (<<<) +- warn: + lhs: Data.Function.fix + note: '''fix'' is already exported from Relude' + name: Use 'fix' from Relude + rhs: fix +- warn: + lhs: Data.Function.on + note: '''on'' is already exported from Relude' + name: Use 'on' from Relude + rhs: 'on' +- warn: + lhs: Data.Bifunctor.Bifunctor + note: '''Bifunctor'' is already exported from Relude' + name: Use 'Bifunctor' from Relude + rhs: Bifunctor +- warn: + lhs: Data.Bifunctor.bimap + note: '''bimap'' is already exported from Relude' + name: Use 'bimap' from Relude + rhs: bimap +- warn: + lhs: Data.Bifunctor.first + note: '''first'' is already exported from Relude' + name: Use 'first' from Relude + rhs: first +- warn: + lhs: Data.Bifunctor.second + note: '''second'' is already exported from Relude' + name: Use 'second' from Relude + rhs: second +- warn: + lhs: Data.Functor.void + note: '''void'' is already exported from Relude' + name: Use 'void' from Relude + rhs: void +- warn: + lhs: (Data.Functor.$>) + note: Operator '($>)' is already exported from Relude + name: Use '$>' from Relude + rhs: ($>) +- warn: + lhs: (Data.Functor.<&>) + note: Operator '(<&>)' is already exported from Relude + name: Use '<&>' from Relude + rhs: (<&>) +- warn: + lhs: Data.Functor.Compose.Compose + note: '''Compose'' is already exported from Relude' + name: Use 'Compose' from Relude + rhs: Compose +- warn: + lhs: Data.Functor.Compose.getCompose + note: '''getCompose'' is already exported from Relude' + name: Use 'getCompose' from Relude + rhs: getCompose +- warn: + lhs: Data.Functor.Identity.Identity + note: '''Identity'' is already exported from Relude' + name: Use 'Identity' from Relude + rhs: Identity +- warn: + lhs: Data.Functor.Identity.runIdentity + note: '''runIdentity'' is already exported from Relude' + name: Use 'runIdentity' from Relude + rhs: runIdentity +- warn: + lhs: Control.Concurrent.MVar.MVar + note: '''MVar'' is already exported from Relude' + name: Use 'MVar' from Relude + rhs: MVar +- warn: + lhs: Control.Concurrent.MVar.newEmptyMVar + note: '''newEmptyMVar'' is already exported from Relude' + name: Use 'newEmptyMVar' from Relude + rhs: newEmptyMVar +- warn: + lhs: Control.Concurrent.MVar.newMVar + note: '''newMVar'' is already exported from Relude' + name: Use 'newMVar' from Relude + rhs: newMVar +- warn: + lhs: Control.Concurrent.MVar.putMVar + note: '''putMVar'' is already exported from Relude' + name: Use 'putMVar' from Relude + rhs: putMVar +- warn: + lhs: Control.Concurrent.MVar.readMVar + note: '''readMVar'' is already exported from Relude' + name: Use 'readMVar' from Relude + rhs: readMVar +- warn: + lhs: Control.Concurrent.MVar.swapMVar + note: '''swapMVar'' is already exported from Relude' + name: Use 'swapMVar' from Relude + rhs: swapMVar +- warn: + lhs: Control.Concurrent.MVar.takeMVar + note: '''takeMVar'' is already exported from Relude' + name: Use 'takeMVar' from Relude + rhs: takeMVar +- warn: + lhs: Control.Concurrent.MVar.tryPutMVar + note: '''tryPutMVar'' is already exported from Relude' + name: Use 'tryPutMVar' from Relude + rhs: tryPutMVar +- warn: + lhs: Control.Concurrent.MVar.tryReadMVar + note: '''tryReadMVar'' is already exported from Relude' + name: Use 'tryReadMVar' from Relude + rhs: tryReadMVar +- warn: + lhs: Control.Concurrent.MVar.tryTakeMVar + note: '''tryTakeMVar'' is already exported from Relude' + name: Use 'tryTakeMVar' from Relude + rhs: tryTakeMVar +- warn: + lhs: Control.Monad.STM.STM + note: '''STM'' is already exported from Relude' + name: Use 'STM' from Relude + rhs: STM +- warn: + lhs: Control.Monad.STM.atomically + note: '''atomically'' is already exported from Relude' + name: Use 'atomically' from Relude + rhs: atomically +- warn: + lhs: Control.Concurrent.STM.TVar.TVar + note: '''TVar'' is already exported from Relude' + name: Use 'TVar' from Relude + rhs: TVar +- warn: + lhs: Control.Concurrent.STM.TVar.newTVarIO + note: '''newTVarIO'' is already exported from Relude' + name: Use 'newTVarIO' from Relude + rhs: newTVarIO +- warn: + lhs: Control.Concurrent.STM.TVar.readTVarIO + note: '''readTVarIO'' is already exported from Relude' + name: Use 'readTVarIO' from Relude + rhs: readTVarIO +- warn: + lhs: Control.Concurrent.STM.TVar.modifyTVar' + note: '''modifyTVar'''' is already exported from Relude' + name: Use 'modifyTVar'' from Relude + rhs: modifyTVar' +- warn: + lhs: Control.Concurrent.STM.TVar.newTVar + note: '''newTVar'' is already exported from Relude' + name: Use 'newTVar' from Relude + rhs: newTVar +- warn: + lhs: Control.Concurrent.STM.TVar.readTVar + note: '''readTVar'' is already exported from Relude' + name: Use 'readTVar' from Relude + rhs: readTVar +- warn: + lhs: Control.Concurrent.STM.TVar.writeTVar + note: '''writeTVar'' is already exported from Relude' + name: Use 'writeTVar' from Relude + rhs: writeTVar +- warn: + lhs: Data.IORef.IORef + note: '''IORef'' is already exported from Relude' + name: Use 'IORef' from Relude + rhs: IORef +- warn: + lhs: Data.IORef.atomicModifyIORef + note: '''atomicModifyIORef'' is already exported from Relude' + name: Use 'atomicModifyIORef' from Relude + rhs: atomicModifyIORef +- warn: + lhs: Data.IORef.atomicModifyIORef' + note: '''atomicModifyIORef'''' is already exported from Relude' + name: Use 'atomicModifyIORef'' from Relude + rhs: atomicModifyIORef' +- warn: + lhs: Data.IORef.atomicWriteIORef + note: '''atomicWriteIORef'' is already exported from Relude' + name: Use 'atomicWriteIORef' from Relude + rhs: atomicWriteIORef +- warn: + lhs: Data.IORef.modifyIORef + note: '''modifyIORef'' is already exported from Relude' + name: Use 'modifyIORef' from Relude + rhs: modifyIORef +- warn: + lhs: Data.IORef.modifyIORef' + note: '''modifyIORef'''' is already exported from Relude' + name: Use 'modifyIORef'' from Relude + rhs: modifyIORef' +- warn: + lhs: Data.IORef.newIORef + note: '''newIORef'' is already exported from Relude' + name: Use 'newIORef' from Relude + rhs: newIORef +- warn: + lhs: Data.IORef.readIORef + note: '''readIORef'' is already exported from Relude' + name: Use 'readIORef' from Relude + rhs: readIORef +- warn: + lhs: Data.IORef.writeIORef + note: '''writeIORef'' is already exported from Relude' + name: Use 'writeIORef' from Relude + rhs: writeIORef +- warn: + lhs: Data.Text.IO.getLine + note: '''getLine'' is already exported from Relude' + name: Use 'getLine' from Relude + rhs: getLine +- warn: + lhs: Data.List.genericDrop + note: '''genericDrop'' is already exported from Relude' + name: Use 'genericDrop' from Relude + rhs: genericDrop +- warn: + lhs: Data.List.genericLength + note: '''genericLength'' is already exported from Relude' + name: Use 'genericLength' from Relude + rhs: genericLength +- warn: + lhs: Data.List.genericReplicate + note: '''genericReplicate'' is already exported from Relude' + name: Use 'genericReplicate' from Relude + rhs: genericReplicate +- warn: + lhs: Data.List.genericSplitAt + note: '''genericSplitAt'' is already exported from Relude' + name: Use 'genericSplitAt' from Relude + rhs: genericSplitAt +- warn: + lhs: Data.List.genericTake + note: '''genericTake'' is already exported from Relude' + name: Use 'genericTake' from Relude + rhs: genericTake +- warn: + lhs: Data.List.group + note: '''group'' is already exported from Relude' + name: Use 'group' from Relude + rhs: group +- warn: + lhs: Data.List.inits + note: '''inits'' is already exported from Relude' + name: Use 'inits' from Relude + rhs: inits +- warn: + lhs: Data.List.intercalate + note: '''intercalate'' is already exported from Relude' + name: Use 'intercalate' from Relude + rhs: intercalate +- warn: + lhs: Data.List.intersperse + note: '''intersperse'' is already exported from Relude' + name: Use 'intersperse' from Relude + rhs: intersperse +- warn: + lhs: Data.List.isPrefixOf + note: '''isPrefixOf'' is already exported from Relude' + name: Use 'isPrefixOf' from Relude + rhs: isPrefixOf +- warn: + lhs: Data.List.permutations + note: '''permutations'' is already exported from Relude' + name: Use 'permutations' from Relude + rhs: permutations +- warn: + lhs: Data.List.sort + note: '''sort'' is already exported from Relude' + name: Use 'sort' from Relude + rhs: sort +- warn: + lhs: Data.List.sortBy + note: '''sortBy'' is already exported from Relude' + name: Use 'sortBy' from Relude + rhs: sortBy +- warn: + lhs: Data.List.sortOn + note: '''sortOn'' is already exported from Relude' + name: Use 'sortOn' from Relude + rhs: sortOn +- warn: + lhs: Data.List.subsequences + note: '''subsequences'' is already exported from Relude' + name: Use 'subsequences' from Relude + rhs: subsequences +- warn: + lhs: Data.List.tails + note: '''tails'' is already exported from Relude' + name: Use 'tails' from Relude + rhs: tails +- warn: + lhs: Data.List.transpose + note: '''transpose'' is already exported from Relude' + name: Use 'transpose' from Relude + rhs: transpose +- warn: + lhs: Data.List.uncons + note: '''uncons'' is already exported from Relude' + name: Use 'uncons' from Relude + rhs: uncons +- warn: + lhs: Data.List.unfoldr + note: '''unfoldr'' is already exported from Relude' + name: Use 'unfoldr' from Relude + rhs: unfoldr +- warn: + lhs: Data.List.NonEmpty.NonEmpty + note: '''NonEmpty'' is already exported from Relude' + name: Use 'NonEmpty' from Relude + rhs: NonEmpty +- warn: + lhs: (Data.List.NonEmpty.:|) + note: Operator '(:|)' is already exported from Relude + name: Use ':|' from Relude + rhs: (:|) +- warn: + lhs: Data.List.NonEmpty.nonEmpty + note: '''nonEmpty'' is already exported from Relude' + name: Use 'nonEmpty' from Relude + rhs: nonEmpty +- warn: + lhs: Data.List.NonEmpty.head + note: '''head'' is already exported from Relude' + name: Use 'head' from Relude + rhs: head +- warn: + lhs: Data.List.NonEmpty.init + note: '''init'' is already exported from Relude' + name: Use 'init' from Relude + rhs: init +- warn: + lhs: Data.List.NonEmpty.last + note: '''last'' is already exported from Relude' + name: Use 'last' from Relude + rhs: last +- warn: + lhs: Data.List.NonEmpty.tail + note: '''tail'' is already exported from Relude' + name: Use 'tail' from Relude + rhs: tail +- warn: + lhs: GHC.Exts.sortWith + note: '''sortWith'' is already exported from Relude' + name: Use 'sortWith' from Relude + rhs: sortWith +- warn: + lhs: Control.Monad.Except.ExceptT + note: '''ExceptT'' is already exported from Relude' + name: Use 'ExceptT' from Relude + rhs: ExceptT +- warn: + lhs: Control.Monad.Except.runExceptT + note: '''runExceptT'' is already exported from Relude' + name: Use 'runExceptT' from Relude + rhs: runExceptT +- warn: + lhs: Control.Monad.Reader.MonadReader + note: '''MonadReader'' is already exported from Relude' + name: Use 'MonadReader' from Relude + rhs: MonadReader +- warn: + lhs: Control.Monad.Reader.Reader + note: '''Reader'' is already exported from Relude' + name: Use 'Reader' from Relude + rhs: Reader +- warn: + lhs: Control.Monad.Reader.ReaderT + note: '''ReaderT'' is already exported from Relude' + name: Use 'ReaderT' from Relude + rhs: ReaderT +- warn: + lhs: Control.Monad.Reader.runReaderT + note: '''runReaderT'' is already exported from Relude' + name: Use 'runReaderT' from Relude + rhs: runReaderT +- warn: + lhs: Control.Monad.Reader.ask + note: '''ask'' is already exported from Relude' + name: Use 'ask' from Relude + rhs: ask +- warn: + lhs: Control.Monad.Reader.asks + note: '''asks'' is already exported from Relude' + name: Use 'asks' from Relude + rhs: asks +- warn: + lhs: Control.Monad.Reader.local + note: '''local'' is already exported from Relude' + name: Use 'local' from Relude + rhs: local +- warn: + lhs: Control.Monad.Reader.reader + note: '''reader'' is already exported from Relude' + name: Use 'reader' from Relude + rhs: reader +- warn: + lhs: Control.Monad.Reader.runReader + note: '''runReader'' is already exported from Relude' + name: Use 'runReader' from Relude + rhs: runReader +- warn: + lhs: Control.Monad.Reader.withReader + note: '''withReader'' is already exported from Relude' + name: Use 'withReader' from Relude + rhs: withReader +- warn: + lhs: Control.Monad.Reader.withReaderT + note: '''withReaderT'' is already exported from Relude' + name: Use 'withReaderT' from Relude + rhs: withReaderT +- warn: + lhs: Control.Monad.State.Strict.MonadState + note: '''MonadState'' is already exported from Relude' + name: Use 'MonadState' from Relude + rhs: MonadState +- warn: + lhs: Control.Monad.State.Strict.State + note: '''State'' is already exported from Relude' + name: Use 'State' from Relude + rhs: State +- warn: + lhs: Control.Monad.State.Strict.StateT + note: '''StateT'' is already exported from Relude' + name: Use 'StateT' from Relude + rhs: StateT +- warn: + lhs: Control.Monad.State.Strict.runStateT + note: '''runStateT'' is already exported from Relude' + name: Use 'runStateT' from Relude + rhs: runStateT +- warn: + lhs: Control.Monad.State.Strict.evalState + note: '''evalState'' is already exported from Relude' + name: Use 'evalState' from Relude + rhs: evalState +- warn: + lhs: Control.Monad.State.Strict.evalStateT + note: '''evalStateT'' is already exported from Relude' + name: Use 'evalStateT' from Relude + rhs: evalStateT +- warn: + lhs: Control.Monad.State.Strict.execState + note: '''execState'' is already exported from Relude' + name: Use 'execState' from Relude + rhs: execState +- warn: + lhs: Control.Monad.State.Strict.execStateT + note: '''execStateT'' is already exported from Relude' + name: Use 'execStateT' from Relude + rhs: execStateT +- warn: + lhs: Control.Monad.State.Strict.get + note: '''get'' is already exported from Relude' + name: Use 'get' from Relude + rhs: get +- warn: + lhs: Control.Monad.State.Strict.gets + note: '''gets'' is already exported from Relude' + name: Use 'gets' from Relude + rhs: gets +- warn: + lhs: Control.Monad.State.Strict.modify + note: '''modify'' is already exported from Relude' + name: Use 'modify' from Relude + rhs: modify +- warn: + lhs: Control.Monad.State.Strict.modify' + note: '''modify'''' is already exported from Relude' + name: Use 'modify'' from Relude + rhs: modify' +- warn: + lhs: Control.Monad.State.Strict.put + note: '''put'' is already exported from Relude' + name: Use 'put' from Relude + rhs: put +- warn: + lhs: Control.Monad.State.Strict.runState + note: '''runState'' is already exported from Relude' + name: Use 'runState' from Relude + rhs: runState +- warn: + lhs: Control.Monad.State.Strict.state + note: '''state'' is already exported from Relude' + name: Use 'state' from Relude + rhs: state +- warn: + lhs: Control.Monad.State.Strict.withState + note: '''withState'' is already exported from Relude' + name: Use 'withState' from Relude + rhs: withState +- warn: + lhs: Control.Monad.Trans.MonadIO + note: '''MonadIO'' is already exported from Relude' + name: Use 'MonadIO' from Relude + rhs: MonadIO +- warn: + lhs: Control.Monad.Trans.MonadTrans + note: '''MonadTrans'' is already exported from Relude' + name: Use 'MonadTrans' from Relude + rhs: MonadTrans +- warn: + lhs: Control.Monad.Trans.lift + note: '''lift'' is already exported from Relude' + name: Use 'lift' from Relude + rhs: lift +- warn: + lhs: Control.Monad.Trans.liftIO + note: '''liftIO'' is already exported from Relude' + name: Use 'liftIO' from Relude + rhs: liftIO +- warn: + lhs: Control.Monad.Trans.Identity.IdentityT + note: '''IdentityT'' is already exported from Relude' + name: Use 'IdentityT' from Relude + rhs: IdentityT +- warn: + lhs: Control.Monad.Trans.Identity.runIdentityT + note: '''runIdentityT'' is already exported from Relude' + name: Use 'runIdentityT' from Relude + rhs: runIdentityT +- warn: + lhs: Control.Monad.Trans.Maybe.MaybeT + note: '''MaybeT'' is already exported from Relude' + name: Use 'MaybeT' from Relude + rhs: MaybeT +- warn: + lhs: Control.Monad.Trans.Maybe.maybeToExceptT + note: '''maybeToExceptT'' is already exported from Relude' + name: Use 'maybeToExceptT' from Relude + rhs: maybeToExceptT +- warn: + lhs: Control.Monad.Trans.Maybe.exceptToMaybeT + note: '''exceptToMaybeT'' is already exported from Relude' + name: Use 'exceptToMaybeT' from Relude + rhs: exceptToMaybeT +- warn: + lhs: Control.Monad.MonadPlus + note: '''MonadPlus'' is already exported from Relude' + name: Use 'MonadPlus' from Relude + rhs: MonadPlus +- warn: + lhs: Control.Monad.mzero + note: '''mzero'' is already exported from Relude' + name: Use 'mzero' from Relude + rhs: mzero +- warn: + lhs: Control.Monad.mplus + note: '''mplus'' is already exported from Relude' + name: Use 'mplus' from Relude + rhs: mplus +- warn: + lhs: Control.Monad.filterM + note: '''filterM'' is already exported from Relude' + name: Use 'filterM' from Relude + rhs: filterM +- warn: + lhs: Control.Monad.forever + note: '''forever'' is already exported from Relude' + name: Use 'forever' from Relude + rhs: forever +- warn: + lhs: Control.Monad.join + note: '''join'' is already exported from Relude' + name: Use 'join' from Relude + rhs: join +- warn: + lhs: Control.Monad.mapAndUnzipM + note: '''mapAndUnzipM'' is already exported from Relude' + name: Use 'mapAndUnzipM' from Relude + rhs: mapAndUnzipM +- warn: + lhs: Control.Monad.mfilter + note: '''mfilter'' is already exported from Relude' + name: Use 'mfilter' from Relude + rhs: mfilter +- warn: + lhs: Control.Monad.replicateM + note: '''replicateM'' is already exported from Relude' + name: Use 'replicateM' from Relude + rhs: replicateM +- warn: + lhs: Control.Monad.replicateM_ + note: '''replicateM_'' is already exported from Relude' + name: Use 'replicateM_' from Relude + rhs: replicateM_ +- warn: + lhs: Control.Monad.zipWithM + note: '''zipWithM'' is already exported from Relude' + name: Use 'zipWithM' from Relude + rhs: zipWithM +- warn: + lhs: Control.Monad.zipWithM_ + note: '''zipWithM_'' is already exported from Relude' + name: Use 'zipWithM_' from Relude + rhs: zipWithM_ +- warn: + lhs: (Control.Monad.<$!>) + note: Operator '(<$!>)' is already exported from Relude + name: Use '<$!>' from Relude + rhs: (<$!>) +- warn: + lhs: (Control.Monad.<=<) + note: Operator '(<=<)' is already exported from Relude + name: Use '<=<' from Relude + rhs: (<=<) +- warn: + lhs: (Control.Monad.=<<) + note: Operator '(=<<)' is already exported from Relude + name: Use '=<<' from Relude + rhs: (=<<) +- warn: + lhs: (Control.Monad.>=>) + note: Operator '(>=>)' is already exported from Relude + name: Use '>=>' from Relude + rhs: (>=>) +- warn: + lhs: Control.Monad.Fail.MonadFail + note: '''MonadFail'' is already exported from Relude' + name: Use 'MonadFail' from Relude + rhs: MonadFail +- warn: + lhs: Data.Maybe.catMaybes + note: '''catMaybes'' is already exported from Relude' + name: Use 'catMaybes' from Relude + rhs: catMaybes +- warn: + lhs: Data.Maybe.fromMaybe + note: '''fromMaybe'' is already exported from Relude' + name: Use 'fromMaybe' from Relude + rhs: fromMaybe +- warn: + lhs: Data.Maybe.isJust + note: '''isJust'' is already exported from Relude' + name: Use 'isJust' from Relude + rhs: isJust +- warn: + lhs: Data.Maybe.isNothing + note: '''isNothing'' is already exported from Relude' + name: Use 'isNothing' from Relude + rhs: isNothing +- warn: + lhs: Data.Maybe.listToMaybe + note: '''listToMaybe'' is already exported from Relude' + name: Use 'listToMaybe' from Relude + rhs: listToMaybe +- warn: + lhs: Data.Maybe.mapMaybe + note: '''mapMaybe'' is already exported from Relude' + name: Use 'mapMaybe' from Relude + rhs: mapMaybe +- warn: + lhs: Data.Maybe.maybeToList + note: '''maybeToList'' is already exported from Relude' + name: Use 'maybeToList' from Relude + rhs: maybeToList +- warn: + lhs: Data.Either.isLeft + note: '''isLeft'' is already exported from Relude' + name: Use 'isLeft' from Relude + rhs: isLeft +- warn: + lhs: Data.Either.isRight + note: '''isRight'' is already exported from Relude' + name: Use 'isRight' from Relude + rhs: isRight +- warn: + lhs: Data.Either.lefts + note: '''lefts'' is already exported from Relude' + name: Use 'lefts' from Relude + rhs: lefts +- warn: + lhs: Data.Either.partitionEithers + note: '''partitionEithers'' is already exported from Relude' + name: Use 'partitionEithers' from Relude + rhs: partitionEithers +- warn: + lhs: Data.Either.rights + note: '''rights'' is already exported from Relude' + name: Use 'rights' from Relude + rhs: rights +- warn: + lhs: Data.Monoid.All + note: '''All'' is already exported from Relude' + name: Use 'All' from Relude + rhs: All +- warn: + lhs: Data.Monoid.getAll + note: '''getAll'' is already exported from Relude' + name: Use 'getAll' from Relude + rhs: getAll +- warn: + lhs: Data.Monoid.Alt + note: '''Alt'' is already exported from Relude' + name: Use 'Alt' from Relude + rhs: Alt +- warn: + lhs: Data.Monoid.getAlt + note: '''getAlt'' is already exported from Relude' + name: Use 'getAlt' from Relude + rhs: getAlt +- warn: + lhs: Data.Monoid.Any + note: '''Any'' is already exported from Relude' + name: Use 'Any' from Relude + rhs: Any +- warn: + lhs: Data.Monoid.getAny + note: '''getAny'' is already exported from Relude' + name: Use 'getAny' from Relude + rhs: getAny +- warn: + lhs: Data.Monoid.Ap + note: '''Ap'' is already exported from Relude' + name: Use 'Ap' from Relude + rhs: Ap +- warn: + lhs: Data.Monoid.getAp + note: '''getAp'' is already exported from Relude' + name: Use 'getAp' from Relude + rhs: getAp +- warn: + lhs: Data.Monoid.Dual + note: '''Dual'' is already exported from Relude' + name: Use 'Dual' from Relude + rhs: Dual +- warn: + lhs: Data.Monoid.getDual + note: '''getDual'' is already exported from Relude' + name: Use 'getDual' from Relude + rhs: getDual +- warn: + lhs: Data.Monoid.Endo + note: '''Endo'' is already exported from Relude' + name: Use 'Endo' from Relude + rhs: Endo +- warn: + lhs: Data.Monoid.appEndo + note: '''appEndo'' is already exported from Relude' + name: Use 'appEndo' from Relude + rhs: appEndo +- warn: + lhs: Data.Monoid.First + note: '''First'' is already exported from Relude' + name: Use 'First' from Relude + rhs: First +- warn: + lhs: Data.Monoid.getFirst + note: '''getFirst'' is already exported from Relude' + name: Use 'getFirst' from Relude + rhs: getFirst +- warn: + lhs: Data.Monoid.Last + note: '''Last'' is already exported from Relude' + name: Use 'Last' from Relude + rhs: Last +- warn: + lhs: Data.Monoid.getLast + note: '''getLast'' is already exported from Relude' + name: Use 'getLast' from Relude + rhs: getLast +- warn: + lhs: Data.Monoid.Product + note: '''Product'' is already exported from Relude' + name: Use 'Product' from Relude + rhs: Product +- warn: + lhs: Data.Monoid.getProduct + note: '''getProduct'' is already exported from Relude' + name: Use 'getProduct' from Relude + rhs: getProduct +- warn: + lhs: Data.Monoid.Sum + note: '''Sum'' is already exported from Relude' + name: Use 'Sum' from Relude + rhs: Sum +- warn: + lhs: Data.Monoid.getSum + note: '''getSum'' is already exported from Relude' + name: Use 'getSum' from Relude + rhs: getSum +- warn: + lhs: Data.Semigroup.Option + note: '''Option'' is already exported from Relude' + name: Use 'Option' from Relude + rhs: Option +- warn: + lhs: Data.Semigroup.getOption + note: '''getOption'' is already exported from Relude' + name: Use 'getOption' from Relude + rhs: getOption +- warn: + lhs: Data.Semigroup.Semigroup + note: '''Semigroup'' is already exported from Relude' + name: Use 'Semigroup' from Relude + rhs: Semigroup +- warn: + lhs: Data.Semigroup.sconcat + note: '''sconcat'' is already exported from Relude' + name: Use 'sconcat' from Relude + rhs: sconcat +- warn: + lhs: Data.Semigroup.stimes + note: '''stimes'' is already exported from Relude' + name: Use 'stimes' from Relude + rhs: stimes +- warn: + lhs: (Data.Semigroup.<>) + note: Operator '(<>)' is already exported from Relude + name: Use '<>' from Relude + rhs: (<>) +- warn: + lhs: Data.Semigroup.WrappedMonoid + note: '''WrappedMonoid'' is already exported from Relude' + name: Use 'WrappedMonoid' from Relude + rhs: WrappedMonoid +- warn: + lhs: Data.Semigroup.cycle1 + note: '''cycle1'' is already exported from Relude' + name: Use 'cycle1' from Relude + rhs: cycle1 +- warn: + lhs: Data.Semigroup.mtimesDefault + note: '''mtimesDefault'' is already exported from Relude' + name: Use 'mtimesDefault' from Relude + rhs: mtimesDefault +- warn: + lhs: Data.Semigroup.stimesIdempotent + note: '''stimesIdempotent'' is already exported from Relude' + name: Use 'stimesIdempotent' from Relude + rhs: stimesIdempotent +- warn: + lhs: Data.Semigroup.stimesIdempotentMonoid + note: '''stimesIdempotentMonoid'' is already exported from Relude' + name: Use 'stimesIdempotentMonoid' from Relude + rhs: stimesIdempotentMonoid +- warn: + lhs: Data.Semigroup.stimesMonoid + note: '''stimesMonoid'' is already exported from Relude' + name: Use 'stimesMonoid' from Relude + rhs: stimesMonoid +- warn: + lhs: Data.ByteString.ByteString + note: '''ByteString'' is already exported from Relude' + name: Use 'ByteString' from Relude + rhs: ByteString +- warn: + lhs: Data.ByteString.Short.ShortByteString + note: '''ShortByteString'' is already exported from Relude' + name: Use 'ShortByteString' from Relude + rhs: ShortByteString +- warn: + lhs: Data.ByteString.Short.toShort + note: '''toShort'' is already exported from Relude' + name: Use 'toShort' from Relude + rhs: toShort +- warn: + lhs: Data.ByteString.Short.fromShort + note: '''fromShort'' is already exported from Relude' + name: Use 'fromShort' from Relude + rhs: fromShort +- warn: + lhs: Data.String.IsString + note: '''IsString'' is already exported from Relude' + name: Use 'IsString' from Relude + rhs: IsString +- warn: + lhs: Data.String.fromString + note: '''fromString'' is already exported from Relude' + name: Use 'fromString' from Relude + rhs: fromString +- warn: + lhs: Data.Text.Text + note: '''Text'' is already exported from Relude' + name: Use 'Text' from Relude + rhs: Text +- warn: + lhs: Data.Text.lines + note: '''lines'' is already exported from Relude' + name: Use 'lines' from Relude + rhs: lines +- warn: + lhs: Data.Text.unlines + note: '''unlines'' is already exported from Relude' + name: Use 'unlines' from Relude + rhs: unlines +- warn: + lhs: Data.Text.words + note: '''words'' is already exported from Relude' + name: Use 'words' from Relude + rhs: words +- warn: + lhs: Data.Text.unwords + note: '''unwords'' is already exported from Relude' + name: Use 'unwords' from Relude + rhs: unwords +- warn: + lhs: Data.Text.Encoding.decodeUtf8' + note: '''decodeUtf8'''' is already exported from Relude' + name: Use 'decodeUtf8'' from Relude + rhs: decodeUtf8' +- warn: + lhs: Data.Text.Encoding.decodeUtf8With + note: '''decodeUtf8With'' is already exported from Relude' + name: Use 'decodeUtf8With' from Relude + rhs: decodeUtf8With +- warn: + lhs: Data.Text.Encoding.Error.OnDecodeError + note: '''OnDecodeError'' is already exported from Relude' + name: Use 'OnDecodeError' from Relude + rhs: OnDecodeError +- warn: + lhs: Data.Text.Encoding.Error.OnError + note: '''OnError'' is already exported from Relude' + name: Use 'OnError' from Relude + rhs: OnError +- warn: + lhs: Data.Text.Encoding.Error.UnicodeException + note: '''UnicodeException'' is already exported from Relude' + name: Use 'UnicodeException' from Relude + rhs: UnicodeException +- warn: + lhs: Data.Text.Encoding.Error.lenientDecode + note: '''lenientDecode'' is already exported from Relude' + name: Use 'lenientDecode' from Relude + rhs: lenientDecode +- warn: + lhs: Data.Text.Encoding.Error.strictDecode + note: '''strictDecode'' is already exported from Relude' + name: Use 'strictDecode' from Relude + rhs: strictDecode +- warn: + lhs: Text.Read.Read + note: '''Read'' is already exported from Relude' + name: Use 'Read' from Relude + rhs: Read +- warn: + lhs: Text.Read.readMaybe + note: '''readMaybe'' is already exported from Relude' + name: Use 'readMaybe' from Relude + rhs: readMaybe +- warn: + lhs: (liftIO (newEmptyMVar )) + note: If you import 'newEmptyMVar' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: newEmptyMVar +- warn: + lhs: (liftIO (newMVar x)) + note: If you import 'newMVar' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: newMVar +- warn: + lhs: (liftIO (putMVar x y)) + note: If you import 'putMVar' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: putMVar +- warn: + lhs: (liftIO (readMVar x)) + note: If you import 'readMVar' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: readMVar +- warn: + lhs: (liftIO (swapMVar x y)) + note: If you import 'swapMVar' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: swapMVar +- warn: + lhs: (liftIO (takeMVar x)) + note: If you import 'takeMVar' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: takeMVar +- warn: + lhs: (liftIO (tryPutMVar x y)) + note: If you import 'tryPutMVar' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: tryPutMVar +- warn: + lhs: (liftIO (tryReadMVar x)) + note: If you import 'tryReadMVar' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: tryReadMVar +- warn: + lhs: (liftIO (tryTakeMVar x)) + note: If you import 'tryTakeMVar' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: tryTakeMVar +- warn: + lhs: (liftIO (atomically x)) + note: If you import 'atomically' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: atomically +- warn: + lhs: (liftIO (newTVarIO x)) + note: If you import 'newTVarIO' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: newTVarIO +- warn: + lhs: (liftIO (readTVarIO x)) + note: If you import 'readTVarIO' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: readTVarIO +- warn: + lhs: (liftIO (exitWith x)) + note: If you import 'exitWith' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: exitWith +- warn: + lhs: (liftIO (exitFailure )) + note: If you import 'exitFailure' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: exitFailure +- warn: + lhs: (liftIO (exitSuccess )) + note: If you import 'exitSuccess' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: exitSuccess +- warn: + lhs: (liftIO (die x)) + note: If you import 'die' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: die +- warn: + lhs: (liftIO (readFile x)) + note: If you import 'readFile' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: readFile +- warn: + lhs: (liftIO (writeFile x y)) + note: If you import 'writeFile' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: writeFile +- warn: + lhs: (liftIO (appendFile x y)) + note: If you import 'appendFile' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: appendFile +- warn: + lhs: (liftIO (readFileText x)) + note: If you import 'readFileText' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: readFileText +- warn: + lhs: (liftIO (writeFileText x y)) + note: If you import 'writeFileText' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: writeFileText +- warn: + lhs: (liftIO (appendFileText x y)) + note: If you import 'appendFileText' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: appendFileText +- warn: + lhs: (liftIO (readFileLText x)) + note: If you import 'readFileLText' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: readFileLText +- warn: + lhs: (liftIO (writeFileLText x y)) + note: If you import 'writeFileLText' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: writeFileLText +- warn: + lhs: (liftIO (appendFileLText x y)) + note: If you import 'appendFileLText' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: appendFileLText +- warn: + lhs: (liftIO (readFileBS x)) + note: If you import 'readFileBS' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: readFileBS +- warn: + lhs: (liftIO (writeFileBS x y)) + note: If you import 'writeFileBS' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: writeFileBS +- warn: + lhs: (liftIO (appendFileBS x y)) + note: If you import 'appendFileBS' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: appendFileBS +- warn: + lhs: (liftIO (readFileLBS x)) + note: If you import 'readFileLBS' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: readFileLBS +- warn: + lhs: (liftIO (writeFileLBS x y)) + note: If you import 'writeFileLBS' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: writeFileLBS +- warn: + lhs: (liftIO (appendFileLBS x y)) + note: If you import 'appendFileLBS' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: appendFileLBS +- warn: + lhs: (liftIO (newIORef x)) + note: If you import 'newIORef' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: newIORef +- warn: + lhs: (liftIO (readIORef x)) + note: If you import 'readIORef' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: readIORef +- warn: + lhs: (liftIO (writeIORef x y)) + note: If you import 'writeIORef' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: writeIORef +- warn: + lhs: (liftIO (modifyIORef x y)) + note: If you import 'modifyIORef' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: modifyIORef +- warn: + lhs: (liftIO (modifyIORef' x y)) + note: If you import 'modifyIORef'' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: modifyIORef' +- warn: + lhs: (liftIO (atomicModifyIORef x y)) + note: If you import 'atomicModifyIORef' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: atomicModifyIORef +- warn: + lhs: (liftIO (atomicModifyIORef' x y)) + note: If you import 'atomicModifyIORef'' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: atomicModifyIORef' +- warn: + lhs: (liftIO (atomicWriteIORef x y)) + note: If you import 'atomicWriteIORef' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: atomicWriteIORef +- warn: + lhs: (liftIO (getLine )) + note: If you import 'getLine' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: getLine +- warn: + lhs: (liftIO (print x)) + note: If you import 'print' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: print +- warn: + lhs: (liftIO (putStr x)) + note: If you import 'putStr' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: putStr +- warn: + lhs: (liftIO (putStrLn x)) + note: If you import 'putStrLn' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: putStrLn +- warn: + lhs: (liftIO (putText x)) + note: If you import 'putText' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: putText +- warn: + lhs: (liftIO (putTextLn x)) + note: If you import 'putTextLn' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: putTextLn +- warn: + lhs: (liftIO (putLText x)) + note: If you import 'putLText' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: putLText +- warn: + lhs: (liftIO (putLTextLn x)) + note: If you import 'putLTextLn' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: putLTextLn +- warn: + lhs: (liftIO (putBS x)) + note: If you import 'putBS' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: putBS +- warn: + lhs: (liftIO (putBSLn x)) + note: If you import 'putBSLn' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: putBSLn +- warn: + lhs: (liftIO (putLBS x)) + note: If you import 'putLBS' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: putLBS +- warn: + lhs: (liftIO (putLBSLn x)) + note: If you import 'putLBSLn' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: putLBSLn +- hint: + lhs: fmap (bimap f g) + note: Use `bimapF` from `Relude.Extra.Bifunctor` + rhs: bimapF f g +- hint: + lhs: bimap f g <$> x + note: Use `bimapF` from `Relude.Extra.Bifunctor` + rhs: bimapF f g x +- hint: + lhs: fmap (first f) + note: Use `firstF` from `Relude.Extra.Bifunctor` + rhs: firstF f +- hint: + lhs: fmap . first + note: Use `firstF` from `Relude.Extra.Bifunctor` + rhs: firstF +- hint: + lhs: fmap (second f) + note: Use `secondF` from `Relude.Extra.Bifunctor` + rhs: secondF f +- hint: + lhs: fmap . second + note: Use `secondF` from `Relude.Extra.Bifunctor` + rhs: secondF +- hint: + lhs: '[minBound .. maxBound]' + note: Use `universe` from `Relude.Extra.Enum` + rhs: universe +- hint: + lhs: succ + note: '`succ` from `Prelude` is a pure function but it may throw exception. Consider + using `next` from `Relude.Extra.Enum` instead.' + rhs: next +- hint: + lhs: pred + note: '`pred` from `Prelude` is a pure function but it may throw exception. Consider + using `prev` from `Relude.Extra.Enum` instead.' + rhs: prev +- hint: + lhs: toEnum + note: '`toEnum` from `Prelude` is a pure function but it may throw exception. + Consider using `safeToEnum` from `Relude.Extra.Enum` instead.' + rhs: safeToEnum +- hint: + lhs: fmap (,a) (f a) + note: Use `traverseToFst` from `Relude.Extra.Tuple` + rhs: traverseToFst f a +- hint: + lhs: fmap (flip (,) a) (f a) + note: Use `traverseToFst` from `Relude.Extra.Tuple` + rhs: traverseToFst f a +- hint: + lhs: (,a) <$> f a + note: Use `traverseToFst` from `Relude.Extra.Tuple` + rhs: traverseToFst f a +- hint: + lhs: flip (,) a <$> f a + note: Use `traverseToFst` from `Relude.Extra.Tuple` + rhs: traverseToFst f a +- hint: + lhs: fmap (a,) (f a) + note: Use `traverseToSnd` from `Relude.Extra.Tuple` + rhs: traverseToSnd f a +- hint: + lhs: fmap ((,) a) (f a) + note: Use `traverseToSnd` from `Relude.Extra.Tuple` + rhs: traverseToSnd f a +- hint: + lhs: (a,) <$> f a + note: Use `traverseToSnd` from `Relude.Extra.Tuple` + rhs: traverseToSnd f a +- hint: + lhs: (,) a <$> f a + note: Use `traverseToSnd` from `Relude.Extra.Tuple` + rhs: traverseToSnd f a diff --git a/apps/kassandra/README.md b/apps/kassandra/README.md new file mode 100644 index 00000000..59daf04a --- /dev/null +++ b/apps/kassandra/README.md @@ -0,0 +1,37 @@ +# kassandra + +*Kassandra from Greek mythology tells the Trojan _warriors_ in the Illiad not to take in the horse offer by the Greek. They donā€˜t listen to her.* + +This is a taskwarrior frontend build with Haskell and reflex-frp. + +**WARNING: This will eat all of your tasks! This app is underdocumented and not intended for use by anyone else, yet.** + +## State + +* This project is primarily for my personal use. I share it in the spirit of free software but it is not primarily intendend to be usable for anyone else, at least at the moment. +* This app provides a GUI to view and edit tasks. +* You can compile the standalone:kassandra2 target with ghc to get a webkit-gtk-app or you can compile with obelisk to get a webserver. (Which needs some authorization right now.) +* Right now there are only a few custom lists to see tasks. The most useful one under the button "List" are lists based on any tag. +* It is supposed to be completely reactive, so everything it shows should be up-to-date. It relies right now on two netcat hooks to get updated from taskwarrior: + +```shell +āÆ cat ~/.task/hooks/on-add.kassandra-notification +#!/bin/bash +tee > (nc 127.0.0.1 6545) +āÆ cat ~/.task/hooks/on-modify.kassandra-notification +#!/bin/bash +tail -n 1 | tee >(nc 127.0.0.1 6545) +``` + +(In the future these hooks might be generated by the program.) + +* Right now the app shows tasks in a tree. Tasks tagged with +root are roots. Tasks with partof: are children. If you dont use this feature you can still see a plain list of tasks and edit them. +* You can sort tasks by dragging and dropping them either in a taglist or as children of a common parent. The sortposition is saved in custom uda attributes. + +## Plans + +* The default UI should use some reasonable search dialog which should more or less fit normal taskwarrior use. +* Some calendar integration to sort tasks by time. +* UI improvements und documentation. The current UI is not self explanatory and has some ugly quirks. +* Custom UDA based features should be deconfigurable. +* There is supposed to be a solid auth system so you can use any client: Android/webkit/web to connect to any server. diff --git a/apps/kassandra/backend/LICENSE b/apps/kassandra/backend/LICENSE new file mode 100644 index 00000000..0ad25db4 --- /dev/null +++ b/apps/kassandra/backend/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/apps/kassandra/backend/backend.cabal b/apps/kassandra/backend/backend.cabal new file mode 100644 index 00000000..64770dfe --- /dev/null +++ b/apps/kassandra/backend/backend.cabal @@ -0,0 +1,106 @@ +cabal-version: 2.4 +name: backend +version: 0.1 + +-- BEGIN dhall generated common configuration +license-file: LICENSE +author: Malte Brandy +maintainer: malte.brandy@maralorn.de +build-type: Simple +extra-source-files: CHANGELOG.md + +common common-config + default-extensions: + AllowAmbiguousTypes + ConstraintKinds + DataKinds + DeriveAnyClass + DeriveGeneric + DuplicateRecordFields + EmptyCase + FlexibleContexts + FlexibleInstances + GADTs + LambdaCase + MultiParamTypeClasses + NamedFieldPuns + OverloadedLabels + OverloadedStrings + PartialTypeSignatures + PatternGuards + PatternSynonyms + QuasiQuotes + RankNTypes + RecursiveDo + ScopedTypeVariables + StandaloneDeriving + StrictData + TemplateHaskell + TupleSections + TypeApplications + TypeFamilies + UndecidableInstances + ViewPatterns + + ghc-options: + -Weverything -Wno-unsafe -Wno-safe -Wno-all-missed-specialisations + -Wno-implicit-prelude -Wno-missed-specialisations + -Wno-monomorphism-restriction -Wno-partial-fields + -Wno-missing-import-lists -Wno-orphans -Wcompat + + mixins: base hiding (Prelude) + default-language: Haskell2010 + +-- END dhall generated common configuration + + +library + import: common-config + hs-source-dirs: src + + if impl(ghcjs -any) + buildable: False + + build-depends: + , aeson + , async + , base + , containers + , dhall + , data-default-class + , frontend + , say + , kassandra + , network-simple + , obelisk-backend + , obelisk-route + , password >=2.0.1.0 + , relude + , snap-core + , standalone + , stm + , taskwarrior + , uuid + , websockets + , websockets-snap + + exposed-modules: + Backend + Backend.Config + +executable backend + import: common-config + main-is: main.hs + hs-source-dirs: src-bin + + if impl(ghcjs -any) + buildable: False + + build-depends: + , backend + , base + , frontend + , kassandra + , obelisk-backend + + ghc-options: -threaded diff --git a/apps/kassandra/backend/frontend.jsexe b/apps/kassandra/backend/frontend.jsexe new file mode 120000 index 00000000..3a9cb6fe --- /dev/null +++ b/apps/kassandra/backend/frontend.jsexe @@ -0,0 +1 @@ +../frontend-js/bin/frontend.jsexe \ No newline at end of file diff --git a/apps/kassandra/backend/frontendJs/frontend.jsexe b/apps/kassandra/backend/frontendJs/frontend.jsexe new file mode 120000 index 00000000..af9b8f45 --- /dev/null +++ b/apps/kassandra/backend/frontendJs/frontend.jsexe @@ -0,0 +1 @@ +../../frontend-js/bin/frontend.jsexe \ No newline at end of file diff --git a/apps/kassandra/backend/hie.yaml b/apps/kassandra/backend/hie.yaml new file mode 100644 index 00000000..d2f855d7 --- /dev/null +++ b/apps/kassandra/backend/hie.yaml @@ -0,0 +1,2 @@ +cradle: + none: diff --git a/apps/kassandra/backend/src-bin/main.hs b/apps/kassandra/backend/src-bin/main.hs new file mode 100644 index 00000000..05182efb --- /dev/null +++ b/apps/kassandra/backend/src-bin/main.hs @@ -0,0 +1,8 @@ +module Main (main) where + +import Backend +import Frontend +import Obelisk.Backend + +main :: IO () +main = runBackend backend frontend diff --git a/apps/kassandra/backend/src/Backend.hs b/apps/kassandra/backend/src/Backend.hs new file mode 100644 index 00000000..6aa4fedd --- /dev/null +++ b/apps/kassandra/backend/src/Backend.hs @@ -0,0 +1,87 @@ +module Backend ( + backend, +) where + +import Backend.Config (BackendConfig, readConfig, users) +import Control.Concurrent.STM.TQueue (TQueue, newTQueueIO, writeTQueue) +import Control.Exception (try) +import qualified Data.Aeson as Aeson +import Data.Map (lookup) +import Data.Password.Argon2 +import Frontend.Route (BackendRoute (..), FrontendRoute, fullRouteEncoder) +import Kassandra.Config (AccountConfig (..)) +import Kassandra.LocalBackend (LocalBackendRequest (..)) +import Kassandra.Standalone.State (localBackendProvider) +import Network.WebSockets (ConnectionException, ServerApp, acceptRequest, forkPingThread, receiveData, rejectRequest, sendTextData) +import Network.WebSockets.Snap (runWebSocketsSnap) +import Obelisk.Backend (Backend (..)) +import Obelisk.Route (R, pattern (:/)) +import Say (say) +import Snap.Core (MonadSnap, Snap) + +backend :: Backend BackendRoute FrontendRoute +backend = + Backend + { _backend_run = serveSnaplet + , _backend_routeEncoder = fullRouteEncoder + } + +serveSnaplet :: ((R BackendRoute -> Snap ()) -> IO b) -> IO () +serveSnaplet serve = do + config <- readConfig Nothing + backendRequestQueue <- newTQueueIO + let backendSnaplet :: MonadSnap m => R BackendRoute -> m () + backendSnaplet = \case + BackendRouteSocket :/ (_, params) -> serveWebsocket config backendRequestQueue params + BackendRouteMissing :/ () -> pass + concurrently_ + (localBackendProvider backendRequestQueue) + (serve backendSnaplet) + +serveWebsocket :: + MonadSnap m => + BackendConfig -> + TQueue LocalBackendRequest -> + Map Text (Maybe Text) -> + m () +serveWebsocket config backendRequestQueue params = + let mayUsername = join (params ^. at "username") + mayPassword = join (params ^. at "password") + mayCreds = liftA2 (,) mayUsername mayPassword + action (Just (username, password)) + | Just userConfig <- lookup username (users config) + , PasswordCheckSuccess <- checkPassword (mkPassword password) (passwordHash userConfig) = + acceptSocket backendRequestQueue username userConfig + action (Just (username, _)) + | Just _ <- lookup username (users config) = + \connection -> do + say [i|Rejecting Websocket request for #{username :: Text}, wrong password.|] + rejectRequest connection "No valid 'username' and 'password' provided." + action _ = \connection -> do + say [i|Rejecting Websocket request #{show mayUsername :: Text}. No matching user found.|] + rejectRequest connection "No valid 'username' and 'password' provided." + in runWebSocketsSnap . action $ mayCreds + +acceptSocket :: TQueue LocalBackendRequest -> Text -> AccountConfig -> ServerApp +acceptSocket backendRequestQueue username accountConfig pendingConnection = do + say [i|Websocket Client by user #{username} connected!|] + connection <- acceptRequest pendingConnection + forkPingThread connection 30 + let responseCallback = sendTextData connection . Aeson.encode + alive <- newTVarIO True + requestQueue <- newTQueueIO + atomically . writeTQueue backendRequestQueue $ + LocalBackendRequest + { userConfig = accountConfig ^. #userConfig + , alive + , responseCallback + , requestQueue + } + let go = + (try (receiveData connection) :: IO (Either ConnectionException LByteString)) >>= \case + Left err -> do + say [i|Socket for #{username} closed. With error #{err}|] + atomically (writeTVar alive False) + Right (Aeson.decode -> msg) -> + concurrently_ go . whenJust msg $ atomically . writeTQueue requestQueue + go diff --git a/apps/kassandra/backend/src/Backend/Config.hs b/apps/kassandra/backend/src/Backend/Config.hs new file mode 100644 index 00000000..ec8965e4 --- /dev/null +++ b/apps/kassandra/backend/src/Backend/Config.hs @@ -0,0 +1,31 @@ +module Backend.Config ( + readConfig, + BackendConfig (..), +) where + +import Dhall (FromDhall) +import Kassandra.Config ( + AccountConfig, + Dict, + ) +import Kassandra.Config.Dhall ( + DhallLoadConfig ( + DhallLoadConfig + ), + defaultConfig, + defaultFile, + envName, + loadDhallConfig, + ) +import Kassandra.Standalone.Config ( + BackendConfig (..), + ) + +readConfig :: Maybe Text -> IO BackendConfig +readConfig = + loadDhallConfig + DhallLoadConfig + { envName = "KASSANDRA_BACKEND_CONFIG" + , defaultFile = "~/.config/kassandra/backend.dhall" + , defaultConfig = "{ = }" + } diff --git a/apps/kassandra/backend/static b/apps/kassandra/backend/static new file mode 120000 index 00000000..4dab1644 --- /dev/null +++ b/apps/kassandra/backend/static @@ -0,0 +1 @@ +../static \ No newline at end of file diff --git a/apps/kassandra/cabal.project b/apps/kassandra/cabal.project new file mode 100644 index 00000000..b4f14481 --- /dev/null +++ b/apps/kassandra/cabal.project @@ -0,0 +1,3 @@ +packages: + kassandra/ + standalone/ diff --git a/apps/kassandra/cabal.project.local b/apps/kassandra/cabal.project.local new file mode 100644 index 00000000..2eab4ee6 --- /dev/null +++ b/apps/kassandra/cabal.project.local @@ -0,0 +1,2 @@ +package * + ghc-options: -fwrite-ide-info diff --git a/apps/kassandra/code.nix b/apps/kassandra/code.nix new file mode 100644 index 00000000..208f42d2 --- /dev/null +++ b/apps/kassandra/code.nix @@ -0,0 +1 @@ +"16" diff --git a/apps/kassandra/common-config.dhall b/apps/kassandra/common-config.dhall new file mode 100644 index 00000000..eba7382b --- /dev/null +++ b/apps/kassandra/common-config.dhall @@ -0,0 +1,71 @@ +let Prelude = https://prelude.dhall-lang.org/v16.0.0/package.dhall + +let extensions = + [ "AllowAmbiguousTypes" + , "BlockArguments" + , "ConstraintKinds" + , "DataKinds" + , "DeriveAnyClass" + , "DeriveGeneric" + , "DerivingStrategies" + , "DuplicateRecordFields" + , "EmptyCase" + , "FlexibleContexts" + , "FlexibleInstances" + , "GADTs" + , "LambdaCase" + , "MultiParamTypeClasses" + , "NamedFieldPuns" + , "OverloadedLabels" + , "OverloadedStrings" + , "PartialTypeSignatures" + , "PatternGuards" + , "PatternSynonyms" + , "QuasiQuotes" + , "RankNTypes" + , "RecursiveDo" + , "ScopedTypeVariables" + , "StandaloneDeriving" + , "StrictData" + , "TemplateHaskell" + , "TupleSections" + , "TypeApplications" + , "TypeFamilies" + , "UndecidableInstances" + , "ViewPatterns" + ] + +let ghc-options = + [ "-Wall" + , "-Wcompat" + , "-Wno-orphans" + , "-Wincomplete-uni-patterns" + , "-Wincomplete-record-updates" + , "-Wmissing-export-lists" + , "-Widentities" + , "-Wredundant-constraints" + , "-Wmissing-home-modules" + ] + +let multiLineList = + Prelude.Text.concatMapSep "\n" Text (Ī»(x : Text) ā†’ " ${x}") + +in '' + -- BEGIN dhall generated common configuration + -- generate with: dhall text --file common-config.dhall + license-file: LICENSE + author: Malte Brandy + maintainer: malte.brandy@maralorn.de + build-type: Simple + extra-source-files: CHANGELOG.md + + common common-config + default-extensions: + ${multiLineList extensions} + ghc-options: + ${multiLineList ghc-options} + mixins: base hiding (Prelude) + default-language: Haskell2010 + -- END dhall generated common configuration + + '' diff --git a/apps/kassandra/config/common/example b/apps/kassandra/config/common/example new file mode 100644 index 00000000..f1db5cb9 --- /dev/null +++ b/apps/kassandra/config/common/example @@ -0,0 +1 @@ +This string comes from config/common/example diff --git a/apps/kassandra/config/common/route b/apps/kassandra/config/common/route new file mode 100644 index 00000000..99298a3d --- /dev/null +++ b/apps/kassandra/config/common/route @@ -0,0 +1 @@ +http://localhost:8000 \ No newline at end of file diff --git a/apps/kassandra/config/readme.md b/apps/kassandra/config/readme.md new file mode 100644 index 00000000..7ca5a54b --- /dev/null +++ b/apps/kassandra/config/readme.md @@ -0,0 +1,9 @@ +### Config + +Obelisk projects should contain a config folder with the following subfolders: common, frontend, and backend. + +Things that should never be transmitted to the frontend belong in backend/ (e.g., email credentials) + +Frontend-only configuration belongs in frontend/. + +Shared configuration files (e.g., the route config) belong in common/ diff --git a/apps/kassandra/default.nix b/apps/kassandra/default.nix new file mode 100644 index 00000000..30ccc367 --- /dev/null +++ b/apps/kassandra/default.nix @@ -0,0 +1,147 @@ +{ obelisk ? import ./.obelisk/impl { + system = builtins.currentSystem; + config.android_sdk.accept_license = true; + } +}: +with obelisk; +project ./. ( + { pkgs, ... }: + let + inherit (pkgs.haskell.lib) + markUnbroken dontCheck addBuildDepend doJailbreak + overrideCabal + ; + in + { + android = { + applicationId = "de.maralorn.kassandra"; + displayName = "Kassandra"; + releaseKey = null; + isRelease = true; + version = { + code = import ./code.nix; + name = "0.1.0"; + }; + }; + overrides = self: super: { + kassandra = overrideCabal super.kassandra { doHaddock = false; }; + backend = addBuildDepend super.backend pkgs.taskwarrior; + clay = markUnbroken (dontCheck super.clay); + haskeline = dontCheck (self.callHackage "haskeline" "0.8.0.1" { }); + repline = doJailbreak (self.callHackage "repline" "0.4.0.0" { }); + dhall = dontCheck (self.callHackage "dhall" "1.35.0" { }); + relude = dontCheck super.relude; + stm-containers = markUnbroken super.stm-containers; + stm-hamt = markUnbroken (doJailbreak super.stm-hamt); + streamly-bytestring = self.callHackageDirect + { + pkg = "streamly-bytestring"; + ver = "0.1.2"; + sha256 = "08xhp8zgf5n1j4v1br1dz9ih8j05vk92swp3nz9in5xajllkc7qv"; + } + { }; + streamly = self.callHackageDirect + { + pkg = "streamly"; + ver = "0.7.0"; + sha256 = "0hr2cz14w6nnbvhnq1fvr8v4rzyqcj3b9khf2rszyji00fmp27l1"; + } + { }; + nonempty-vector = self.callHackageDirect + { + pkg = "nonempty-vector"; + ver = "0.1.0.0"; + sha256 = "06abdmdy9z0w6ishiibir3qfjpqxmb4mrkhgyc4j58hd14s8rj0x"; + } + { }; + nonempty-containers = self.callHackageDirect + { + pkg = "nonempty-containers"; + ver = "0.3.4.1"; + sha256 = "0nbnr0az201lv09dwcxcppkfc9b05kyw4la990z5asn9737pvpr2"; + } + { }; + iCalendar = overrideCabal (doJailbreak (markUnbroken super.iCalendar)) { + preConfigure = ''substituteInPlace iCalendar.cabal --replace "network >=2.6 && <2.7" "network -any"''; + }; + prettyprinter = self.callHackageDirect + { + pkg = "prettyprinter"; + ver = "1.5.1"; + sha256 = "0wx01rvgwnnmg10sh9x2whif5z12058w5djh7m5swz94wvkg5cg3"; + } + { }; + cborg-json = self.callHackageDirect + { + pkg = "cborg-json"; + ver = "0.2.2.0"; + sha256 = "1s7pv3jz8s1qb0ydcc5nra9f63jp4ay4d0vncv919bakf8snj4vw"; + } + { }; + generic-random = self.callHackageDirect + { + pkg = "generic-random"; + ver = "1.3.0.0"; + sha256 = "0m7lb40wgmyszv8l6qmarkfgs8r0idgl9agwsi72236hpvp353ad"; + } + { }; + atomic-write = self.callHackageDirect + { + pkg = "atomic-write"; + ver = "0.2.0.7"; + sha256 = "1r9ckwljdbw3mi8rmzmsnh89z8nhw2qnds9n271gkjgavb6hxxf3"; + } + { }; + taskwarrior = self.callHackageDirect + { + pkg = "taskwarrior"; + ver = "0.5.0.0"; + sha256 = "sha256-elDUtz0NSG4WHxkyCQ1CunYXWIVRj6EqkKSchPy+c3E="; + } + { }; + base64 = self.callHackageDirect + { + pkg = "base64"; + ver = "0.4.1"; + sha256 = "1pz9s8bmnkrrr3v5mhkwv8vaf251vmxs87zzc5nsjsa027j9lr22"; + } + { }; + password = self.callHackageDirect + { + pkg = "password"; + ver = "2.0.1.0"; + sha256 = "1q99v7w6bdfpnw245aa3zaj3x7mhl9i2y7f2rzlc30g066p9jhaz"; + } + { }; + indexed-profunctors = self.callHackageDirect + { + pkg = "indexed-profunctors"; + ver = "0.1"; + sha256 = "0vpgbymfhnvip90jwvyniqi34lhz5n3ni1f21g81n5rap0q140za"; + } + { }; + generic-lens-core = self.callHackageDirect + { + pkg = "generic-lens-core"; + ver = "2.0.0.0"; + sha256 = "07parw0frqxxkjbbas9m9xb3pmpqrx9wz63m35wa6xqng9vlcscm"; + } + { }; + generic-optics = self.callHackageDirect + { + pkg = "generic-optics"; + ver = "2.0.0.0"; + sha256 = "0xy5k5b35w1i1zxy0dv5fk1b3zrd3hx3v5kh593k2la7ri880wmq"; + } + { }; + optics-core = self.callHackage "optics-core" "0.3.0.1" { }; + optics-th = self.callHackage "optics-th" "0.3.0.2" { }; + optics-extra = self.callHackage "optics-extra" "0.3" { }; + optics = self.callHackage "optics" "0.3" { }; + }; + packages = { + kassandra = ./kassandra; + standalone = ./standalone; + }; + } +) diff --git a/apps/kassandra/frontend/LICENSE b/apps/kassandra/frontend/LICENSE new file mode 100644 index 00000000..0ad25db4 --- /dev/null +++ b/apps/kassandra/frontend/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/apps/kassandra/frontend/frontend.cabal b/apps/kassandra/frontend/frontend.cabal new file mode 100644 index 00000000..07cc5a75 --- /dev/null +++ b/apps/kassandra/frontend/frontend.cabal @@ -0,0 +1,104 @@ +cabal-version: 2.4 +name: frontend +version: 0.1 + +-- BEGIN dhall generated common configuration +license-file: LICENSE +author: Malte Brandy +maintainer: malte.brandy@maralorn.de +build-type: Simple +extra-source-files: CHANGELOG.md + +common common-config + default-extensions: + AllowAmbiguousTypes + ConstraintKinds + DataKinds + DeriveAnyClass + DeriveGeneric + DuplicateRecordFields + EmptyCase + FlexibleContexts + FlexibleInstances + GADTs + LambdaCase + MultiParamTypeClasses + NamedFieldPuns + OverloadedLabels + OverloadedStrings + PartialTypeSignatures + PatternGuards + QuasiQuotes + RankNTypes + RecursiveDo + ScopedTypeVariables + StandaloneDeriving + StrictData + TemplateHaskell + TupleSections + TypeApplications + TypeFamilies + UndecidableInstances + ViewPatterns + + ghc-options: + -Weverything -Wno-unsafe -Wno-safe -Wno-all-missed-specialisations + -Wno-implicit-prelude -Wno-missed-specialisations + -Wno-monomorphism-restriction -Wno-partial-fields + -Wno-missing-import-lists -Wno-orphans -Wcompat + + mixins: base hiding (Prelude) + default-language: Haskell2010 + +-- END dhall generated common configuration + + +library + import: common-config + hs-source-dirs: src + build-depends: + , aeson + , base + , dependent-map + , dependent-sum-template + , extra + , generic-optics + , jsaddle + , kassandra + , mtl + , obelisk-executable-config-lookup + , obelisk-frontend + , obelisk-generated-static + , obelisk-route + , optics + , optics-th + , reflex + , reflex-dom + , scientific + , taskwarrior + , text + , these + , time + , unordered-containers + , uuid + , relude + + exposed-modules: + Frontend + Frontend.Route + +executable frontend + import: common-config + main-is: main.hs + hs-source-dirs: src-bin + build-depends: + , base + , frontend + , kassandra + , obelisk-frontend + , obelisk-generated-static + , obelisk-route + , reflex-dom + + ghc-options: -threaded + mixins: base hiding (Prelude) diff --git a/apps/kassandra/frontend/hie.yaml b/apps/kassandra/frontend/hie.yaml new file mode 100644 index 00000000..d2f855d7 --- /dev/null +++ b/apps/kassandra/frontend/hie.yaml @@ -0,0 +1,2 @@ +cradle: + none: diff --git a/apps/kassandra/frontend/src-bin/main.hs b/apps/kassandra/frontend/src-bin/main.hs new file mode 100644 index 00000000..b08064fb --- /dev/null +++ b/apps/kassandra/frontend/src-bin/main.hs @@ -0,0 +1,12 @@ +module Main (main) where + +import Frontend +import Frontend.Route +import Obelisk.Frontend +import Obelisk.Route.Frontend +import Reflex.Dom + +main :: IO () +main = do + let Right validFullEncoder = checkEncoder fullRouteEncoder + run $ runFrontend validFullEncoder frontend diff --git a/apps/kassandra/frontend/src/Frontend.hs b/apps/kassandra/frontend/src/Frontend.hs new file mode 100644 index 00000000..14f3f778 --- /dev/null +++ b/apps/kassandra/frontend/src/Frontend.hs @@ -0,0 +1,39 @@ +module Frontend ( + frontend, +) where + +import Obelisk.Frontend +import Obelisk.Generated.Static +import Obelisk.Route + +import Frontend.Route (FrontendRoute) +import Kassandra.Css (cssAsText) +import Kassandra.MainWidget (mainWidget) +import Kassandra.RemoteBackendWidget ( + CloseEvent (..), + remoteBackendWidget, + ) +import Kassandra.Types +import qualified Reflex.Dom as D +import Relude.Extra.Newtype + +-- This runs in a monad that can be run on the client or the server. +-- To run code in a pure client or pure server context, use one of the +-- `prerender` functions. +frontend :: Frontend (R FrontendRoute) +frontend = + Frontend + { _frontend_head = frontendHead + , _frontend_body = void $ D.prerender pass frontendBody + } + +frontendBody :: WidgetJSM t m => m () +frontendBody = D.dyn_ . fmap (maybe pass mainWidget) + =<< remoteBackendWidget (wrap D.never) Nothing + +css = cssAsText (static @"MaterialIcons-Regular-Outlined.otf") + +frontendHead :: ObeliskWidget js t route m => m () +frontendHead = do + D.el "title" $ D.text "Kassandra 2 Webversion" + D.elAttr "style" mempty . D.text $ css diff --git a/apps/kassandra/frontend/src/Frontend/Route.hs b/apps/kassandra/frontend/src/Frontend/Route.hs new file mode 100644 index 00000000..b0351051 --- /dev/null +++ b/apps/kassandra/frontend/src/Frontend/Route.hs @@ -0,0 +1,55 @@ +module Frontend.Route ( + BackendRoute (..), + FrontendRoute (..), + fullRouteEncoder, +) where + +{- -- You will probably want these imports for composing Encoders. +import Prelude hiding (id, (.)) +import Control.Category +-} + +import qualified Control.Category +import Data.Text (Text) + +--import Data.Functor.Identity + +import Obelisk.Route +import Obelisk.Route.TH + +data BackendRoute :: Type -> Type where + -- | Used to handle unparseable routes. + BackendRouteMissing :: BackendRoute () + BackendRouteSocket :: BackendRoute PageName + +-- You can define any routes that will be handled specially by the backend here. +-- i.e. These do not serve the frontend, but do something different, such as serving static files. + +data FrontendRoute :: Type -> Type where + FrontendRouteMain :: FrontendRoute () + +-- This type is used to define frontend routes, i.e. ones for which the backend will serve the frontend. + +fullRouteEncoder :: + Encoder + (Either Text) + Identity + (R (FullRoute BackendRoute FrontendRoute)) + PageName +fullRouteEncoder = + mkFullRouteEncoder + (FullRoute_Backend BackendRouteMissing :/ ()) + ( \case + BackendRouteMissing -> PathSegment "missing" $ unitEncoder mempty + BackendRouteSocket -> PathSegment "socket" $ Control.Category.id + ) + ( \case + FrontendRouteMain -> PathEnd $ unitEncoder mempty + ) + +concat + <$> mapM + deriveRouteComponent + [ ''BackendRoute + , ''FrontendRoute + ] diff --git a/apps/kassandra/hie.yaml b/apps/kassandra/hie.yaml new file mode 100644 index 00000000..70a2d898 --- /dev/null +++ b/apps/kassandra/hie.yaml @@ -0,0 +1,2 @@ +cradle: + cabal: diff --git a/apps/kassandra/kassandra/CHANGELOG.md b/apps/kassandra/kassandra/CHANGELOG.md new file mode 100644 index 00000000..8bdf153b --- /dev/null +++ b/apps/kassandra/kassandra/CHANGELOG.md @@ -0,0 +1,5 @@ +# Revision history for kassandra + +## 0.1.0.0 -- YYYY-mm-dd + +* First version. Released on an unsuspecting world. diff --git a/apps/kassandra/kassandra/LICENSE b/apps/kassandra/kassandra/LICENSE new file mode 100644 index 00000000..0ad25db4 --- /dev/null +++ b/apps/kassandra/kassandra/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/apps/kassandra/kassandra/Setup.hs b/apps/kassandra/kassandra/Setup.hs new file mode 100644 index 00000000..e8ef27db --- /dev/null +++ b/apps/kassandra/kassandra/Setup.hs @@ -0,0 +1,3 @@ +import Distribution.Simple + +main = defaultMain diff --git a/apps/kassandra/kassandra/kassandra.cabal b/apps/kassandra/kassandra/kassandra.cabal new file mode 100644 index 00000000..07c1086f --- /dev/null +++ b/apps/kassandra/kassandra/kassandra.cabal @@ -0,0 +1,124 @@ +cabal-version: 2.4 +name: kassandra +version: 0.1.0.0 + +-- BEGIN dhall generated common configuration +-- generate with: dhall text --file common-config.dhall +license-file: LICENSE +author: Malte Brandy +maintainer: malte.brandy@maralorn.de +build-type: Simple +extra-source-files: CHANGELOG.md + +common common-config + default-extensions: + AllowAmbiguousTypes + ConstraintKinds + DataKinds + DeriveAnyClass + DeriveGeneric + DerivingStrategies + DuplicateRecordFields + EmptyCase + FlexibleContexts + FlexibleInstances + GADTs + GeneralizedNewtypeDeriving + LambdaCase + MultiParamTypeClasses + NamedFieldPuns + OverloadedLabels + OverloadedStrings + PartialTypeSignatures + PatternGuards + PatternSynonyms + QuasiQuotes + RankNTypes + RecursiveDo + ScopedTypeVariables + StandaloneDeriving + StrictData + TemplateHaskell + TupleSections + TypeApplications + TypeFamilies + UndecidableInstances + ViewPatterns + + ghc-options: + -Wall -Wcompat -Wno-orphans -Wincomplete-uni-patterns + -Wincomplete-record-updates -Wmissing-export-lists -Widentities + -Wredundant-constraints -Wmissing-home-modules + + mixins: base hiding (Prelude) + default-language: Haskell2010 + +-- END dhall generated common configuration + + +library + import: common-config + exposed-modules: + Kassandra.Api + Kassandra.Calendar + Kassandra.Config + Kassandra.Css + Kassandra.Debug + Kassandra.LocalBackend + Kassandra.LocalBackendWidget + Kassandra.MainWidget + Kassandra.RemoteBackendWidget + Kassandra.SelectorWidget + Kassandra.State + Kassandra.Types + Kassandra.Util + Prelude + + other-modules: + Kassandra.AgendaWidget + Kassandra.BaseWidgets + Kassandra.DragAndDrop + Kassandra.ListElementWidget + Kassandra.ListWidget + Kassandra.LogWidget + Kassandra.ReflexUtil + Kassandra.Sorting + Kassandra.TaskWidget + Kassandra.TextEditWidget + Kassandra.TimeWidgets + + build-depends: + , aeson + , ansi-terminal + , async + , base + , clay + , containers + , data-default-class + , extra + , generic-optics + , jsaddle + , jsaddle-dom + , nonempty-containers + , optics + , optics-th + , password + , patch + , process + , reflex + , reflex-dom + , relude + , say + , scientific + , stm + , string-interpolate + , taskwarrior + , template-haskell + , text + , these + , time + , unordered-containers + , uuid + , witherable + + hs-source-dirs: src diff --git a/apps/kassandra/kassandra/src/Kassandra/AgendaWidget.hs b/apps/kassandra/kassandra/src/Kassandra/AgendaWidget.hs new file mode 100644 index 00000000..ab2bddac --- /dev/null +++ b/apps/kassandra/kassandra/src/Kassandra/AgendaWidget.hs @@ -0,0 +1,98 @@ +{-# LANGUAGE BlockArguments #-} + +module Kassandra.AgendaWidget (agendaWidget) where + +import qualified Data.Sequence as Seq +import qualified Data.Sequence.NonEmpty as NESeq +import Kassandra.BaseWidgets (br, button, icon) +import Kassandra.Calendar ( + CalendarEvent ( + CalendarEvent, + calendarName, + comment, + description, + location, + time, + todoList, + uid + ), + CalendarList (entries), + EventTime (AllDayEvent, SimpleEvent), + switchToCurrentZone, + ) +import Kassandra.Config (DefinitionElement (..), ListItem (..)) +import Kassandra.DragAndDrop (insertArea) +import Kassandra.ListElementWidget (AdhocContext (..), definitionElementWidget, tellList) +import Kassandra.ReflexUtil (listWithGaps) +import Kassandra.TextEditWidget (createTextWidget) +import Kassandra.Types (StandardWidget, Widget, getAppState) +import qualified Reflex as R +import qualified Reflex.Dom as D + +agendaWidget :: StandardWidget t m r e => m () +agendaWidget = do + appState <- getAppState + let calendarEvents = appState ^. #calendarEvents + void $ + D.dyn_ $ + calendarEvents <&> mapM_ \CalendarEvent{description, time, calendarName, location, comment, uid, todoList} -> + D.divClass "event" $ do + icon "" "event" + D.text description + whenJust location \l -> do + br + icon "" "room" + D.text l + whenJust comment \c -> do + br + icon "" "comment" + D.text c + br + icon "" "schedule" + printEventTime time + br + icon "" "list" + D.text calendarName + br + calendarListWidget uid todoList + +calendarListWidget :: StandardWidget t m r e => Text -> CalendarList -> m () +calendarListWidget uid calendarList = do + listWithGaps widget gapWidget (pure (entries calendarList)) + newTaskEvent <- createTextWidget (button "selector" $ D.text "New Task") + tellList uid $ newTaskEvent <&> \content -> (#entries %~ (Seq.|> ListElement (AdHocTask content))) calendarList + where + widget definitionElement = D.divClass "definitionElement" do + D.divClass "definitionUI" $ do + delete <- button "" (D.text "x") + tellList uid $ delete $> (#entries %~ Seq.filter (definitionElement /=)) calendarList + D.divClass "element" $ definitionElementWidget (AgendaEvent uid calendarList) definitionElement + gapWidget around = do + evs <- insertArea (pure mempty) $ icon "dropHere above" "forward" + tellList uid $ R.attachWith (flip insertedCalendarList) (R.current around) evs + insertedCalendarList toInsert = \case + (Nothing, Nothing) -> updateOnList (const (toSeq toInsert)) + (Just _, Nothing) -> updateOnList (<> toSeq toInsert) + (Nothing, Just _) -> updateOnList (toSeq toInsert <>) + (Just _, Just after) -> updateOnList ((\(a, b) -> a <> toSeq toInsert <> b) . Seq.breakl (== after)) + where + updateOnList upd = (#entries %~ upd . Seq.filter (isNothing . flip NESeq.elemIndexL toInsert)) calendarList + +printEventTime :: Widget t m => EventTime -> m () +printEventTime (SimpleEvent start end) = do + showstart <- switchToCurrentZone (start ^. #time) + showend <- switchToCurrentZone (end ^. #time) + let printFullTime = toText . formatTime defaultTimeLocale "%a %Y-%m-%d %H:%M" + let printEndTime = toText . formatTime defaultTimeLocale "%H:%M" + let zone = start ^. #zone + D.text (printFullTime showstart) + icon "" "arrow_right_alt" + D.text (printEndTime showend) + D.text [i|(Defined in #{zone})|] +printEventTime (AllDayEvent startDay endDay) = do + let printFullTime = toText . formatTime defaultTimeLocale "%a %Y-%m-%d" + D.text (printFullTime startDay) + when (startDay /= endDay) do + icon "" "arrow_right_alt" + D.text (printFullTime endDay) +printEventTime _ = pass diff --git a/apps/kassandra/kassandra/src/Kassandra/Api.hs b/apps/kassandra/kassandra/src/Kassandra/Api.hs new file mode 100644 index 00000000..1f54ec76 --- /dev/null +++ b/apps/kassandra/kassandra/src/Kassandra/Api.hs @@ -0,0 +1,29 @@ +module Kassandra.Api ( + SocketMessage (..), + SocketRequest (..), +) where + +import Kassandra.Calendar +import Kassandra.Config (UIConfig) + +type SocketError = Text + +data SocketMessage + = TaskUpdates (NESeq Task) + | CalendarEvents (Seq CalendarEvent) + | UIConfigResponse UIConfig + | SocketError SocketError + | ConnectionEstablished + deriving stock (Show, Read, Generic) + deriving anyclass (ToJSON, FromJSON) +makePrismLabels ''SocketMessage + +data SocketRequest + = UIConfigRequest + | AllTasks + | CalenderRequest + | ChangeTasks (NESeq Task) + | SetCalendarList Text CalendarList + deriving stock (Show, Read, Eq, Generic) + deriving anyclass (ToJSON, FromJSON) +makePrismLabels ''SocketRequest diff --git a/apps/kassandra/kassandra/src/Kassandra/BaseWidgets.hs b/apps/kassandra/kassandra/src/Kassandra/BaseWidgets.hs new file mode 100644 index 00000000..b1a65bb2 --- /dev/null +++ b/apps/kassandra/kassandra/src/Kassandra/BaseWidgets.hs @@ -0,0 +1,32 @@ +module Kassandra.BaseWidgets ( + br, + icon, + button, + stateWidget, +) where + +import Kassandra.Types (Widget) +import qualified Reflex as R +import qualified Reflex.Dom as D +import Relude.Extra.Bifunctor (secondF) + +br :: D.DomBuilder t m => m () +br = D.el "br" pass + +stateWidget :: + Widget t m => + state -> + (state -> m (R.Event t a, R.Event t state)) -> + m (R.Event t a) +stateWidget initialState widget = do + eventsEvent <- D.workflowView $ stateToWorkflow initialState + R.switchHold R.never eventsEvent + where + stateToWorkflow = D.Workflow . secondF (fmap stateToWorkflow) . widget + +icon :: Widget t m => Text -> Text -> m () +icon cssClass = D.elClass "i" ("material-icons icon " <> cssClass) . D.text + +button :: Widget t m => Text -> m () -> m (R.Event t ()) +button cssClass = + fmap (D.domEvent D.Click . fst) . D.elClass' "span" ("button " <> cssClass) diff --git a/apps/kassandra/kassandra/src/Kassandra/Calendar.hs b/apps/kassandra/kassandra/src/Kassandra/Calendar.hs new file mode 100644 index 00000000..837c8fd8 --- /dev/null +++ b/apps/kassandra/kassandra/src/Kassandra/Calendar.hs @@ -0,0 +1,76 @@ +module Kassandra.Calendar ( + CalendarEvent (..), + CalendarList (..), + EventTime (..), + TZTime (..), + sortEvents, + tzTimeToUTC, + zonedDay, + switchToCurrentZone, +) where + +import Data.Time +import Kassandra.Config + +data TZTime = TZTime + { time :: ZonedTime + , zone :: Text + } + deriving stock (Show, Read, Generic) + deriving anyclass (ToJSON, FromJSON) +makeLabels ''TZTime + +data EventTime + = SimpleEvent {start :: TZTime, end :: TZTime} + | AllDayEvent {startDay :: Day, endDay :: Day} + | RecurringEvent + deriving stock (Show, Read, Generic) + deriving anyclass (ToJSON, FromJSON) +makeLabels ''EventTime + +data CalendarList = CalendarList + { entries :: Seq DefinitionElement + , completed :: Set Text + } + deriving stock (Eq, Show, Read, Generic) + deriving anyclass (ToJSON, FromJSON) +makeLabels ''CalendarList + +data CalendarEvent = CalendarEvent + { uid :: Text + , time :: EventTime + , description :: Text + , location :: Maybe Text + , comment :: Maybe Text + , todoList :: CalendarList + , calendarName :: Text + } + deriving stock (Show, Read, Generic) + deriving anyclass (ToJSON, FromJSON) +makeLabels ''CalendarEvent + +sortEvents :: CalendarEvent -> CalendarEvent -> Ordering +sortEvents = sortEventTimes `on` (^. #time) + +sortEventTimes :: EventTime -> EventTime -> Ordering +sortEventTimes lhs rhs = case (lhs, rhs) of + (SimpleEvent startTimeLhs _, SimpleEvent startTimeRhs _) -> compare (tzTimeToUTC startTimeLhs) (tzTimeToUTC startTimeRhs) + (AllDayEvent startDayLhs _, AllDayEvent startDayRhs _) -> compare startDayLhs startDayRhs + (AllDayEvent startDayLhs _, SimpleEvent startTimeRhs _) -> case compare startDayLhs (tzTimeDay startTimeRhs) of EQ -> LT; a -> a + (SimpleEvent startTimeLhs _, AllDayEvent startDayRhs _) -> case compare (tzTimeDay startTimeLhs) startDayRhs of EQ -> GT; a -> a + (_, _) -> EQ + +switchToCurrentZone :: MonadIO m => ZonedTime -> m ZonedTime +switchToCurrentZone time = do + let inUtc = zonedTimeToUTC time + zone <- liftIO $ getTimeZone inUtc + pure $ utcToZonedTime zone inUtc + +tzTimeToUTC :: TZTime -> UTCTime +tzTimeToUTC = zonedTimeToUTC . (^. #time) + +tzTimeDay :: TZTime -> Day +tzTimeDay = localDay . zonedTimeToLocalTime . (^. #time) + +zonedDay :: ZonedTime -> Day +zonedDay = localDay . zonedTimeToLocalTime diff --git a/apps/kassandra/kassandra/src/Kassandra/Config.hs b/apps/kassandra/kassandra/src/Kassandra/Config.hs new file mode 100644 index 00000000..0c3b5ded --- /dev/null +++ b/apps/kassandra/kassandra/src/Kassandra/Config.hs @@ -0,0 +1,216 @@ +module Kassandra.Config ( + AccountConfig (..), + RemoteBackend (..), + NamedBackend (..), + UserConfig (..), + LocalBackend (..), + Dict, + UIConfig (..), + PortConfig (..), + Widget (..), + TreeOption (..), + ListItem (..), + HabiticaTask (..), + HabiticaList (..), + DefinitionElement (..), + ListQuery, + Query, + QueryFilter (..), + TaskProperty (..), + UIFeatures (..), + PasswordConfig (..), + NamedListQuery (..), + TaskwarriorOption (..), +) where + +import Data.Default.Class ( Default(..) ) +import Data.Password.Argon2 ( + Argon2, + PasswordHash, + ) + +type Dict = Map Text + +data UIFeatures = UIFeatures + { sortInTag :: Bool + , treeOption :: TreeOption + } + deriving stock (Show, Eq, Ord, Generic, Read) + deriving anyclass (ToJSON, FromJSON) + +data TreeOption = NoTree | PartOfTree | DependsTree + deriving stock (Show, Eq, Ord, Generic, Read) + deriving anyclass (ToJSON, FromJSON) + +data HabiticaTask = HabiticaDaily | HabiticaTodo + deriving stock (Show, Eq, Ord, Generic, Read) + deriving anyclass (ToJSON, FromJSON) + +data ListItem + = TaskwarriorTask {uuid :: UUID} + | AdHocTask {description :: Text} + | HabiticaTask {task :: HabiticaTask} + | Mail {id :: Text} + deriving stock (Show, Eq, Ord, Generic, Read) + deriving anyclass (ToJSON, FromJSON) +makePrismLabels ''ListItem + +data HabiticaList = HabiticaDailys | HabiticaTodos + deriving stock (Show, Eq, Ord, Generic, Read) + deriving anyclass (ToJSON, FromJSON) + +data TaskProperty + = DescriptionMatches {filter :: Text} + | ParentBlocked + | Blocked + | Waiting + | Pending + | Completed + | Deleted + | IsParent + | OnList + | HasTag {tag :: Text} + | HasParent + deriving stock (Show, Eq, Ord, Generic, Read) + deriving anyclass (ToJSON, FromJSON) + +data TaskwarriorOption = TaskwarriorOption + { name :: Text + , value :: Text + } + deriving stock (Show, Eq, Ord, Generic) + deriving anyclass (Hashable) + +data QueryFilter + = HasProperty {property :: TaskProperty} + | HasntProperty {property :: TaskProperty} + deriving stock (Show, Eq, Ord, Generic, Read) + deriving anyclass (ToJSON, FromJSON) + +type Query = Seq QueryFilter + +data DefinitionElement + = ConfigList {name :: Text, limit :: Maybe Natural} + | ListElement {item :: ListItem} + | QueryList {query :: Query} + | TagList {name :: Text} + | ChildrenList {uuid :: UUID} + | DependenciesList {uuid :: UUID} + | HabiticaList {list :: HabiticaList} + | Mails + deriving stock (Show, Eq, Ord, Generic, Read) + deriving anyclass (ToJSON, FromJSON) +makePrismLabels ''DefinitionElement + +data Widget + = SearchWidget + | DefinitionElementWidget {name :: Text, definitionElement :: DefinitionElement} + deriving stock (Show, Eq, Ord, Generic, Read) + deriving anyclass (ToJSON, FromJSON) + +type ListQuery = Seq DefinitionElement + +data NamedListQuery = NamedListQuery + { name :: Text + , list :: ListQuery + } + deriving stock (Show, Eq, Ord, Generic, Read) + deriving anyclass (ToJSON, FromJSON) + +data UIConfig = UIConfig + { viewList :: Seq Widget + , configuredLists :: Seq NamedListQuery + , uiFeatures :: UIFeatures + } + deriving stock (Show, Eq, Ord, Generic, Read) + deriving anyclass (ToJSON, FromJSON) +makeLabels ''UIConfig + +instance Default UIConfig where + def = + UIConfig + { viewList = mempty + , configuredLists = mempty + , uiFeatures = UIFeatures True PartOfTree + } + +data PortConfig = Port {port :: Word16} | PortRange {min :: Word16, max :: Word16} + deriving stock (Show, Eq, Ord, Generic) + +data PasswordConfig = Prompt | Password {plaintext :: Text} | PasswordCommand {command :: Text} + deriving stock (Show, Eq, Ord, Generic) + +data LocalBackend + = TaskwarriorBackend + { -- | Set config file + taskRcPath :: Maybe Text + , -- | Set task data directory + taskDataPath :: Maybe Text + , -- | Override config variables + taskConfig :: Seq TaskwarriorOption + , -- | Path to taskwarrior binary. Nothing => Lookup "task" from PATH + taskBin :: Maybe Text + , -- | Use the first free port from the given range for the taskwarrior hook listener. + hookListenPort :: PortConfig + , -- | Created hooks are called ".on-add.." and ".on-remove.." + hookSuffix :: Text + , -- | Ensure existence of taskwarrior hook on every start + createHooksOnStart :: Bool + , -- | Remove hook on exit. + removeHooksOnExit :: Bool + } + | GitBackend + { directoryPath :: Text + , commit :: Bool + , configureMerge :: Bool + , createIfMissing :: Bool + , origin :: Maybe Text + , pushOnWrite :: Bool + , watchFiles :: Bool + , pullTimerSeconds :: Maybe Natural + } + deriving stock (Show, Eq, Ord, Generic) + +instance Default LocalBackend where + def = + TaskwarriorBackend + { taskRcPath = Nothing + , taskDataPath = Nothing + , taskConfig = mempty + , taskBin = Nothing + , hookListenPort = Port 6545 + , hookSuffix = "kassandra" + , createHooksOnStart = True + , removeHooksOnExit = True + } + +data RemoteBackend a = RemoteBackend + { url :: Text + , user :: Text + , password :: a + } + deriving stock (Show, Eq, Ord, Generic) + +data NamedBackend b = NamedBackend + { name :: Text + , backend :: b + } + deriving stock (Show, Eq, Ord, Generic) + +data UserConfig = UserConfig + { localBackend :: LocalBackend + , uiConfig :: UIConfig + } + deriving stock (Show, Eq, Ord, Generic) + +instance Default UserConfig where + def = UserConfig def def + +data AccountConfig = AccountConfig + { passwordHash :: PasswordHash Argon2 + , userConfig :: UserConfig + , filterTag :: Text + } + deriving stock (Show, Eq, Ord, Generic) + +makeLabels ''AccountConfig diff --git a/apps/kassandra/kassandra/src/Kassandra/Css.hs b/apps/kassandra/kassandra/src/Kassandra/Css.hs new file mode 100644 index 00000000..300d2f57 --- /dev/null +++ b/apps/kassandra/kassandra/src/Kassandra/Css.hs @@ -0,0 +1,166 @@ +module Kassandra.Css ( + cssAsBS, + cssAsText, +) where + +import Clay +import Prelude hiding ( + i, + (&), + (|>), + ) + +cssToBS :: Css -> ByteString +cssToBS = encodeUtf8 . cssToText + +cssToText :: Css -> Text +cssToText = toStrict . render + +cssAsBS :: ByteString +cssAsBS = $$([||cssToBS (css (Just ""))||]) + +cssAsText :: Text -> Text +cssAsText fontPath = cssToText (css (Just fontPath)) + +fontName :: Text +fontName = "Material Icons" + +css :: Maybe Text -> Css +css fontPath = do + whenJust fontPath $ \fontSrc -> do + fontFace $ do + fontFamily [fontName] [] + fontFaceSrc [FontFaceSrcUrl fontSrc (Just OpenType)] + let --darkBlue = rgb 0 0 33 + lightBlue = rgb 200 200 255 + noMargin = margin (px 0) (px 0) (px 0) (px 0) + noPadding = padding (px 0) (px 0) (px 0) (px 0) + star ? do + fontFamily ["B612"] [] + noMargin + noPadding + body ? do + background white + color black + minHeight (pct 100) + ".definitionElement" ? do + clear clearLeft + ".definitionUI" ? do + float floatLeft + ".remoteBackend" ? do + textAlign (alignSide sideRight) + ".loginDialog" ? do + textAlign (alignSide sideCenter) + padding (em 5) (em 5) (em 5) (em 5) + ".header" ? do + padding (em 0.2) (em 0.2) (em 0.2) (em 0.2) + background lightBlue + fontWeight bold + fontSize (em 1.5) + ".content" ? do + paddingBottom (em 4) + ".footer" ? do + position fixed + bottom (px 0) + width (pct 100) + padding (em 0.22) (em 0.2) (em 0.2) (em 0.2) + background (grayish 200) + color black + ".container" ? do + display flex + minHeight (pct 100) + ".dropHere" ? do + position absolute + background white + color black + border (em 0.1) solid black + let offset = 2 + ".plusOne" ? marginLeft (em offset) + ".plusTwo" ? marginLeft (em (offset * 2)) + ".above" ? marginTop (em (-0.8)) + ".pane" ? width (pct 100) + let buttonPadding = + padding (em 0.3) (em 0.5) (em 0.3) (em 0.5) + buttonCss = do + display inlineBlock + margin (px 1) (px 1) (px 1) (px 1) + buttonPadding + border (em 0.1) solid black + active & do + background black + color white + ".button" ? buttonCss + ".selector" ? buttonCss + --".tag" ? ".icon" ? do + --position absolute + --borderRadius tagRadius tagRadius tagRadius tagRadius + --background lightBlue + --marginLeft (em (-1.1)) + --marginTop (em 0.70) + --fontSize (em 0.85) + ".material-icons" ? do + fontFamily [fontName] [] + fontWeight normal + fontStyle normal + fontSize (em 1) + display inlineBlock + lineHeight (em 1) + width (em 1) + textTransform none + letterSpacing normal + wordWrap normal + whiteSpace nowrap + direction ltr + "-webkit-font-smoothing" -: "antialiased" + ".path" ? do + color grey + fontSize (em 0.8) + let radius = em 0.3 + leftBarWidth = em 1.8 + ".activeEdit" ? buttonPadding + ".event" ? do + border (px 1) solid black + ".task" ? do + color (rgb 0 0 33) + border (px 1) solid black + background white + ".task" ? do + noMargin + ".parentPath" ? display none + ".righttask" ? do + width (pct 100) + ".statusWrapper" ? do + background black + width leftBarWidth + minWidth leftBarWidth + ".uppertask" ? do + display flex + ".edit" ? visibility visible + i ? cursor cursorDefault + ".icon" ? padding radius radius radius radius + ".children" ? do + padding (px 0) (px 0) (px 0) leftBarWidth + background black + --".slimButton" ? do + --marginRight (px (-5)) + --marginLeft (px (-5)) + let blockSize = do + width (em 1) + height (em 1) + bg = grayish 255 + ".checkbox" ? do + marginTop (em 0.28) + marginBottom (em 0.28) + marginLeft (em 0.25) + display inlineBlock + fontSize (em 1.2) + blockSize + borderRadius (em 0.2) (em 0.2) (em 0.2) (em 0.2) + background bg + i ? do + ".hide" & color bg + ".grey" & color (grayish 160) + ".show" & color black + ".showable" & display none + active & i ? do + background black diff --git a/apps/kassandra/kassandra/src/Kassandra/Debug.hs b/apps/kassandra/kassandra/src/Kassandra/Debug.hs new file mode 100644 index 00000000..9170c587 --- /dev/null +++ b/apps/kassandra/kassandra/src/Kassandra/Debug.hs @@ -0,0 +1,164 @@ +module Kassandra.Debug ( + log, + logShow, + logR, + logRShow, + setLogLevel, + Severity (..), +) where + +import Control.Concurrent ( + ThreadId, + modifyMVar, + myThreadId, + ) +import GHC.Stack ( + SrcLoc (SrcLoc), + srcLocModule, + srcLocStartLine, + ) +import Reflex as R +import System.IO.Unsafe (unsafePerformIO) + +import qualified Debug.Trace as Trace +import System.Console.ANSI ( + Color (..), + ColorIntensity (Vivid), + ConsoleLayer (Foreground), + SGR (..), + setSGRCode, + ) +import Relude.Extra.Bifunctor +import Relude.Extra.Enum +import Say + +data Severity = Debug | Info | Warning | Error deriving stock (Show, Read, Eq, Ord) + +class ReflexLoggable l where + useLogString :: (Text -> a -> Text) -> l a -> l a + +instance R.Reflex t => ReflexLoggable (R.Dynamic t) where + useLogString f d = + let e' = traceEventWith (toString . f "updated Dynamic") $ updated d + getV0 = do + x <- sample $ current d + Trace.trace (toString $ f "initialized Dynamic" x) $ return x + in unsafeBuildDynamic getV0 e' + +instance R.Reflex t => ReflexLoggable (R.Event t) where + useLogString f e = traceEventWith (toString . f "triggered Event") e + +logR :: + (HasCallStack, MonadIO m, ReflexLoggable l) => + Severity -> + (a -> Text) -> + l a -> + m (l a) +logR severity decorate loggable = do + isSevere <- severeEnough severity + if isSevere + then do + myId <- liftIO $ modifyMVar traceID $ \a -> pure (next a, a) + withFrozenCallStack $ log Debug ("Registering eventTrace " <> show myId) + let f comment value = + formatMessage + Message + { msgSeverity = severity + , msgCallStack = callStack + , msgThreadId = unsafePerformIO myThreadId + , msgTime = unsafePerformIO getZonedTime + , msgComment = Just (comment <> " " <> show myId) + , msgContent = decorate value + } + pure $ useLogString f loggable + else pure loggable + +logRShow :: + (HasCallStack, MonadIO m, ReflexLoggable l, Show a) => + Severity -> + l a -> + m (l a) +logRShow a = withFrozenCallStack (logR a show) + +logShow :: (HasCallStack, Show a, MonadIO m) => Severity -> a -> m () +logShow s = withFrozenCallStack (log s . show) + +log :: (HasCallStack, MonadIO m) => Severity -> Text -> m () +log severity text = do + thread <- liftIO myThreadId + time <- liftIO getZonedTime + whenM (severeEnough severity) . say . formatMessage $ + Message + { msgSeverity = severity + , msgCallStack = callStack + , msgThreadId = thread + , msgTime = time + , msgComment = Nothing + , msgContent = text + } + +{-# NOINLINE logLevel #-} +logLevel :: MVar (Maybe Severity) +logLevel = unsafePerformIO . newMVar $ Just Warning + +{-# NOINLINE traceID #-} +traceID :: MVar Int +traceID = unsafePerformIO . newMVar $ 0 + +setLogLevel :: Maybe Severity -> IO () +setLogLevel = void . swapMVar logLevel + +severeEnough :: MonadIO m => Severity -> m Bool +severeEnough severity = severeEnough' <$> readMVar logLevel + where + severeEnough' (Just minSeverity) | minSeverity <= severity = True + severeEnough' _ = False + +data Message = Message + { msgSeverity :: !Severity + , msgCallStack :: !CallStack + , msgThreadId :: !ThreadId + , msgTime :: !ZonedTime + , msgComment :: !(Maybe Text) + , msgContent :: !Text + } + +formatMessage :: Message -> Text +formatMessage Message{msgSeverity, msgTime, msgCallStack, msgThreadId, msgComment, msgContent} = + showSeverity msgSeverity + <> showTime msgTime + <> showSourceLoc msgCallStack + <> square (show msgThreadId) + <> maybe "" square msgComment + <> msgContent + +square :: Text -> Text +square t = "[" <> t <> "] " + +-- [30 May 2020 16:44:03.534 +00:00] +showTime :: ZonedTime -> Text +showTime = square . toText . formatTime defaultTimeLocale "%F %T%3Q %z" + +-- | Formats severity in different colours with alignment. +showSeverity :: Severity -> Text +showSeverity = \case + Debug -> color Green "[Debug] " + Info -> color Blue "[Info] " + Warning -> color Yellow "[Warning] " + Error -> color Red "[Error] " + where + color :: Color -> Text -> Text + color c txt = + toText (setSGRCode [SetColor Foreground Vivid c]) <> txt + <> toText + (setSGRCode [Reset]) + +showSourceLoc :: CallStack -> Text +showSourceLoc = square . showCallStack . firstF toText . getCallStack + where + showCallStack = \case + [] -> "" + [(name, SrcLoc{srcLocModule, srcLocStartLine})] -> + name <> "@" <> toText srcLocModule <> "#" <> show srcLocStartLine + (_, SrcLoc{srcLocModule, srcLocStartLine}) : (callerName, _) : _ -> + toText srcLocModule <> "." <> callerName <> "#" <> show srcLocStartLine diff --git a/apps/kassandra/kassandra/src/Kassandra/DragAndDrop.hs b/apps/kassandra/kassandra/src/Kassandra/DragAndDrop.hs new file mode 100644 index 00000000..118c5eac --- /dev/null +++ b/apps/kassandra/kassandra/src/Kassandra/DragAndDrop.hs @@ -0,0 +1,83 @@ +module Kassandra.DragAndDrop ( + childDropArea, + taskDropArea, + tellSelected, + insertArea, +) where + +import Kassandra.Config (DefinitionElement) +import Kassandra.Debug ( + Severity (..), + logRShow, + ) +import Kassandra.Sorting ( + SortPosition, + saveSorting, + ) +import Kassandra.Types ( + AppStateChange, + DataChange, + SelectState, + StandardWidget, + TaskInfos, + WriteApp, + getSelectState, + getTasks, + ) +import Kassandra.Util ( + lookupTask, + tellSingleton, + ) +import qualified Reflex as R +import qualified Reflex.Dom as D + +tellSelected :: (MonadIO m, WriteApp t m e) => R.Event t (Seq DefinitionElement) -> m () +tellSelected = tellSingleton . fmap (_Typed @AppStateChange % _Typed @SelectState #) <=< logRShow Info + +insertArea :: StandardWidget t m r e => R.Dynamic t (Seq DefinitionElement) -> m () -> m (R.Event t (NESeq DefinitionElement)) +insertArea blacklistD areaW = do + selectStateD <- getSelectState + let dropActive = do + selectState <- selectStateD + blacklist <- blacklistD + pure $ if all (`notElem` blacklist) selectState then nonEmptySeq selectState else Nothing + evEv <- + D.dyn $ + dropActive <&> \case + Just entry -> do + dropEl <- fmap fst <$> D.element "span" D.def $ areaW + let event = D.domEvent D.Click dropEl + tellSelected (mempty <$ event) + pure $ entry <$ event + Nothing -> pure R.never + R.switchHold R.never evEv + +taskDropArea :: + StandardWidget t m r e => + R.Dynamic t (Seq UUID) -> + m () -> + (R.Event t (NESeq TaskInfos) -> R.Event t (NESeq Task)) -> + m () +taskDropArea blacklistD areaW handler = do + tasksD <- getTasks + let blackListDefinitionElements = (#_ListElement % #_TaskwarriorTask #) <<$>> blacklistD + insertEvent <- insertArea blackListDefinitionElements areaW + let droppedTaskEvent = + R.attachWithMaybe + (\tasks -> nonEmptySeq . mapMaybe (lookupTask tasks <=< (^? #_ListElement % #_TaskwarriorTask)) . toSeq) + (R.current tasksD) + insertEvent + R.tellEvent $ + fmap (_Typed @AppStateChange % _Typed @DataChange % #_ChangeTask #) + <$> handler droppedTaskEvent + +childDropArea :: + StandardWidget t m r e => + SortPosition t -> + R.Dynamic t (Seq UUID) -> + m () -> + m () +childDropArea pos blacklistD areaW = + taskDropArea blacklistD areaW $ + saveSorting (pos ^. #mode) (pos ^. #list) + . R.attachWith (\u t -> ((^. #task) <$> t, u)) (pos ^. #before) diff --git a/apps/kassandra/kassandra/src/Kassandra/ListElementWidget.hs b/apps/kassandra/kassandra/src/Kassandra/ListElementWidget.hs new file mode 100644 index 00000000..451ab614 --- /dev/null +++ b/apps/kassandra/kassandra/src/Kassandra/ListElementWidget.hs @@ -0,0 +1,128 @@ +{-# LANGUAGE BlockArguments #-} + +module Kassandra.ListElementWidget ( + definitionElementWidget, + configListWidget, + AdhocContext (..), + tellList, + queryWidget, + selectWidget, +) where + +import qualified Data.HashMap.Strict as HashMap +import qualified Data.Sequence as Seq +import qualified Data.Set as Set +import Kassandra.BaseWidgets (br, button, icon) +import Kassandra.Calendar (CalendarList, completed) +import Kassandra.Config ( + DefinitionElement ( + ChildrenList, + ConfigList, + DependenciesList, + HabiticaList, + ListElement, + Mails, + QueryList, + TagList + ), + ListItem (AdHocTask, HabiticaTask, Mail, TaskwarriorTask), + ListQuery, + NamedListQuery (NamedListQuery), + ) +import Kassandra.DragAndDrop (tellSelected) +import Kassandra.ReflexUtil (smartSimpleList) +import Kassandra.Sorting (SortMode (SortModeTag), sortTasks) +import Kassandra.TaskWidget ( + taskList, + taskTreeWidget, + uuidWidget, + ) +import Kassandra.TextEditWidget (createTextWidget) +import Kassandra.Types (AppStateChange, DataChange (SetEventList), StandardWidget, TaskInfos, TaskState, getAppState, getSelectState, getTasks) +import Kassandra.Util (tellNewTask, tellSingleton) +import qualified Reflex as R +import qualified Reflex.Dom as D + +data AdhocContext = NoContext | AgendaEvent Text CalendarList | AgendaList Text (Set Text) + +selectWidget :: StandardWidget t m r e => DefinitionElement -> m () +selectWidget definitionElement = do + (dragEl, _) <- D.elClass' "span" "button" $ icon "" "filter_list" + selectStateB <- toggleContainElement definitionElement <<$>> R.current <$> getSelectState + tellSelected $ R.tag selectStateB (D.domEvent D.Click dragEl) + where + toggleContainElement :: DefinitionElement -> Seq DefinitionElement -> Seq DefinitionElement + toggleContainElement entry selectedTasks = + Seq.findIndexL (== entry) selectedTasks & maybe (selectedTasks |> entry) (`Seq.deleteAt` selectedTasks) + +listElementWidget :: StandardWidget t m r e => AdhocContext -> ListItem -> m () +listElementWidget context = \case + TaskwarriorTask uuid -> uuidWidget taskTreeWidget (pure uuid) + AdHocTask t -> adhocTaskWidget t context + HabiticaTask _ -> error "HabiticaTasks are not yet supported" + Mail _ -> error "Mails are not yet supported" + +configListWidget :: forall t m r e. StandardWidget t m r e => AdhocContext -> Text -> Maybe Natural -> m () +configListWidget context name limit = do + D.text name >> br + namedListQueries <- getAppState ^. mapping (#uiConfig % mapping #configuredLists) + D.dyn_ $ namedListQueries <&> maybe (D.text [i|No list with name "#{name}" configured.|]) (queryWidget context) . getWidget + where + getWidget = Seq.lookup 0 . mapMaybe f + f (NamedListQuery x query) | x == name = Just query + f _ = Nothing + +queryWidget :: StandardWidget t m r e => AdhocContext -> ListQuery -> m () +queryWidget context els = smartSimpleList ((>> br) . definitionElementWidget context) (pure els) + +tasksToShow :: Text -> TaskState -> Seq TaskInfos +tasksToShow tag = filter inList . fromList . HashMap.elems + where + inList :: TaskInfos -> Bool + inList ((^. #task) -> task) = tag `Set.member` (task ^. #tags) && has (#status % #_Pending) task + +definitionElementWidget :: StandardWidget t m r e => AdhocContext -> DefinitionElement -> m () +definitionElementWidget context el = do + selectWidget el + el & \case + ConfigList name limit -> configListWidget context name limit + ListElement el' -> listElementWidget context el' + QueryList query -> D.text "QueryLists not implemented" + (TagList tag) -> + do + D.text tag + tasks <- getTasks + let showTasks = tasksToShow tag <$> tasks + let sortMode = SortModeTag tag + taskList + (R.constant sortMode) + (sortTasks sortMode <$> showTasks) + (R.constDyn IsEmpty) + taskTreeWidget + tellNewTask . fmap (,#tags %~ Set.insert tag) + =<< createTextWidget + (button "selector" $ D.text "Add task to list") + (ChildrenList uuid) -> D.text "ChildrenList not implemented" + (DependenciesList uuid) -> D.text "DependenciesList not implemented" + (HabiticaList list) -> D.text "HabiticaList not implemented" + Mails -> D.text "Mails not implemented" + +adhocTaskWidget :: StandardWidget t m r e => Text -> AdhocContext -> m () +adhocTaskWidget description = \case + AgendaEvent uid calendarList -> do + changeDoneStatus <- checkBox (completed calendarList) + D.text [i| #{description}|] + tellList uid $ + changeDoneStatus <&> \case + True -> (#completed %~ Set.insert description) calendarList + False -> (#completed %~ Set.delete description) calendarList + AgendaList _ completed -> do + checkBox completed >> text + NoContext -> + checkBox mempty >> text + where + checkBox completed = if description `Set.member` completed then (False <$) <$> button "" (D.text "[x]") else (True <$) <$> button "" (D.text "[ ]") + text = D.text [i| #{description}|] + +tellList :: StandardWidget t m r e => Text -> D.Event t CalendarList -> m () +tellList uid listEvent = tellSingleton $ (_Typed @AppStateChange % _Typed @DataChange #) . SetEventList uid <$> listEvent diff --git a/apps/kassandra/kassandra/src/Kassandra/ListWidget.hs b/apps/kassandra/kassandra/src/Kassandra/ListWidget.hs new file mode 100644 index 00000000..65853c06 --- /dev/null +++ b/apps/kassandra/kassandra/src/Kassandra/ListWidget.hs @@ -0,0 +1,52 @@ +module Kassandra.ListWidget ( + listsWidget, + listWidget, +) where + +import qualified Data.HashMap.Strict as HashMap +import Kassandra.Config (DefinitionElement (TagList)) +import Kassandra.ListElementWidget (AdhocContext (NoContext), definitionElementWidget) +import Kassandra.Types ( + StandardWidget, + TaskState, + Widget, + getTasks, + ) +import qualified Reflex as R +import qualified Reflex.Dom as D + +listsWidget :: (StandardWidget t m r e) => m () +listsWidget = do + taskState <- getTasks + D.text "Select a list" + list <- listSelector (getLists <$> taskState) + maybeList <- R.maybeDyn list + D.dyn_ $ maybeList <&> maybe (D.text "Select a list") listWidget + where + getLists :: TaskState -> Seq Text + getLists = + fromList + . toList + . foldMap (^. #tags) + . filter (has $ #status % #_Pending) + . (^. mapping #task) + . HashMap.elems + listSelector :: + (Widget t m) => R.Dynamic t (Seq Text) -> m (R.Dynamic t (Maybe Text)) + listSelector lists = D.el "div" $ do + buttons <- D.dyn $ mapM listButton <$> lists + buttonSum <- R.switchHold R.never $ R.leftmost . toList <$> buttons + R.holdDyn Nothing (Just <$> buttonSum) + listButton :: Widget t m => Text -> m (R.Event t Text) + listButton tag = + fmap ((tag <$) . D.domEvent D.Click . fst) + . D.elClass' "a" "selector" + . D.text + $ tag + +listWidget :: + forall t m r e. StandardWidget t m r e => R.Dynamic t Text -> m () +listWidget list = D.dyn_ (innerRenderList <$> list) + where + innerRenderList :: Text -> m () + innerRenderList tag = definitionElementWidget NoContext (TagList tag) diff --git a/apps/kassandra/kassandra/src/Kassandra/LocalBackend.hs b/apps/kassandra/kassandra/src/Kassandra/LocalBackend.hs new file mode 100644 index 00000000..bf9c78a2 --- /dev/null +++ b/apps/kassandra/kassandra/src/Kassandra/LocalBackend.hs @@ -0,0 +1,41 @@ +module Kassandra.LocalBackend ( + localClientSocket, + LocalBackendRequest (LocalBackendRequest, userConfig, alive, responseCallback, requestQueue), + BackendError (..), +) where + +import Control.Concurrent.STM (TQueue, newTQueueIO, writeTQueue) +import Kassandra.Api (SocketMessage, SocketRequest) +import Kassandra.Config (UserConfig) +import Kassandra.State (ClientSocket) +import Kassandra.Types (WidgetIO) +import qualified Reflex as R + +data LocalSocketState = LocalError Text | SettingUp deriving (Show) +makePrismLabels ''LocalSocketState + +newtype BackendError = BackendError Text + +data LocalBackendRequest = LocalBackendRequest + { userConfig :: UserConfig + , alive :: TVar Bool + , responseCallback :: SocketMessage -> IO () + , requestQueue :: TQueue SocketRequest + } +makeLabels ''LocalBackendRequest + +localClientSocket :: + WidgetIO t m => + TQueue LocalBackendRequest -> + UserConfig -> + m (ClientSocket t m) +localClientSocket requestsQueue userConfig = do + requestQueue <- liftIO newTQueueIO + (socketMessageEvent, responseCallback) <- R.newTriggerEvent + alive <- newTVarIO True + let backendRequest = LocalBackendRequest{userConfig, alive, responseCallback, requestQueue} + clientSocket socketRequestEvent = do + R.performEvent_ $ atomically . mapM_ (writeTQueue requestQueue) <$> socketRequestEvent + pure (pure socketMessageEvent) + atomically $ writeTQueue requestsQueue backendRequest + pure clientSocket diff --git a/apps/kassandra/kassandra/src/Kassandra/LocalBackendWidget.hs b/apps/kassandra/kassandra/src/Kassandra/LocalBackendWidget.hs new file mode 100644 index 00000000..d4587c3a --- /dev/null +++ b/apps/kassandra/kassandra/src/Kassandra/LocalBackendWidget.hs @@ -0,0 +1,22 @@ +module Kassandra.LocalBackendWidget ( + localBackendWidget, +) where + +import Control.Concurrent.STM (TQueue) +import Kassandra.Config ( + NamedBackend (NamedBackend, backend), + UserConfig, + ) +import Kassandra.LocalBackend (LocalBackendRequest, localClientSocket) +import Kassandra.State (StateProvider, makeStateProvider) +import Kassandra.Types (WidgetIO) +import qualified Reflex as R + +localBackendWidget :: + WidgetIO t m => + TQueue LocalBackendRequest -> + NamedBackend UserConfig -> + m (R.Dynamic t (Maybe (StateProvider t m))) +localBackendWidget requestsQueue NamedBackend{backend} = + -- TODO: Use the uiConfig + pure . pure . makeStateProvider <$> localClientSocket requestsQueue backend diff --git a/apps/kassandra/kassandra/src/Kassandra/LogWidget.hs b/apps/kassandra/kassandra/src/Kassandra/LogWidget.hs new file mode 100644 index 00000000..1bd3f354 --- /dev/null +++ b/apps/kassandra/kassandra/src/Kassandra/LogWidget.hs @@ -0,0 +1,4 @@ +module Kassandra.LogWidget (logWidget) where + +logWidget :: Monad m => m () +logWidget = pass diff --git a/apps/kassandra/kassandra/src/Kassandra/MainWidget.hs b/apps/kassandra/kassandra/src/Kassandra/MainWidget.hs new file mode 100644 index 00000000..0d9a0a96 --- /dev/null +++ b/apps/kassandra/kassandra/src/Kassandra/MainWidget.hs @@ -0,0 +1,200 @@ +{-# LANGUAGE PatternSynonyms #-} + +module Kassandra.MainWidget ( + mainWidget, +) where + +import qualified Data.HashMap.Strict as HashMap +import qualified Data.Sequence as Seq +import qualified Data.Sequence.NonEmpty as NESeq +import qualified Data.Set as Set +import Kassandra.AgendaWidget (agendaWidget) +import Kassandra.BaseWidgets (button, br) +import Kassandra.Calendar (CalendarEvent) +import Kassandra.Config (DefinitionElement, Widget (DefinitionElementWidget, SearchWidget)) +import Kassandra.Debug ( + Severity (..), + log, + logR, + setLogLevel, + ) +import Kassandra.ListElementWidget (AdhocContext (NoContext), definitionElementWidget) +import Kassandra.ListWidget (listsWidget) +import Kassandra.LogWidget (logWidget) +import Kassandra.State (StateProvider) +import Kassandra.TaskWidget (taskTreeWidget) +import Kassandra.TextEditWidget (createTextWidget) +import Kassandra.Types ( + AppState (AppState), + AppStateChange, + StandardWidget, + TaskInfos, + TaskState, + WidgetIO, + getAppState, + getSelectState, + getTasks, + getTime, + ) +import Kassandra.Util (lookupTasks, stillTodo, tellNewTask) +import qualified Reflex as R +import qualified Reflex.Dom as D + +mainWidget :: WidgetIO t m => StateProvider t m -> m () +mainWidget stateProvider = do + -- TODO: Use ui Config + liftIO $ setLogLevel $ Just Info + log Info "Loaded Mainwidget" + time <- liftIO getZonedTime + timeDyn <- + fmap (utcToZonedTime (zonedTimeZone time) . (^. lensVL R.tickInfo_lastUTC)) + <$> R.clockLossy 1 (zonedTimeToUTC time) + rec let (appChangeEvents, dataChangeEvents) = + R.fanThese $ partitionEithersNESeq <$> stateChanges + appData <- stateProvider dataChangeEvents + selectedDyn <- R.holdDyn mempty $ NESeq.last <$> appChangeEvents + uiConfig <- R.holdUniqDyn $ appData ^. mapping #uiConfig + (_, stateChanges' :: R.Event t (NESeq AppStateChange)) <- + R.runEventWriterT $ + runReaderT + ( do + taskDiagnosticsWidget + D.divClass "content" widgetSwitcher + infoFooter + ) + ( AppState + (appData ^. mapping #taskState) + timeDyn + selectedDyn + (appData ^. mapping #calendarData) + uiConfig + ) + stateChanges <- logR Info (\a -> [i|StateChange: #{a}|]) stateChanges' + pass + +infoFooter :: StandardWidget t m r e => m () +infoFooter = D.divClass "footer" $ do + selectedState <- getSelectState + D.dyn_ $ + selectedState <&> \a -> do + whenJust (nonEmptySeq a) $ \(selectedTasks :: NESeq DefinitionElement) -> do + forM_ selectedTasks $ \t -> D.divClass "selectedTask" (definitionElementWidget NoContext t) + tellNewTask . fmap (,id) + =<< createTextWidget + (button "selector" $ D.text "New Task") + tasks <- getTasks + D.el "p" $ + D.dynText $ + tasks <&> \taskMap -> + let taskList = HashMap.elems taskMap + countTasks a = length . filter (has (#task % #status % a)) + pending = countTasks #_Pending taskList + completed = countTasks #_Completed taskList + in [i|#{pending} pending and #{completed} completed tasks. Kassandra-ToDo-Management|] + +taskDiagnosticsWidget :: StandardWidget t m r e => m () +taskDiagnosticsWidget = do + tasks <- getTasks + D.dynText $ do + tasksMap <- tasks + let uuids = HashMap.keys tasksMap + hasLoop :: Seq UUID -> UUID -> Maybe UUID + hasLoop seen new + | new `elem` seen = Just new + | otherwise = Seq.lookup 0 (mapMaybe (hasLoop (new <| seen)) nexts) + where + nexts = maybe mempty (^. #children) $ HashMap.lookup new tasksMap + pure $ + firstJust (hasLoop mempty) uuids & \case + Just uuid -> "Found a loop for uuid " <> show uuid + Nothing -> "" -- everything fine + +widgets :: StandardWidget t m r e => Seq (Text, m ()) +widgets = + fromList + [ ("Agenda", agendaWidget) + , ("Sort", nextWidget) + , ("Tag Lists", listsWidget) + , ("Logs", logWidget) + ] + +widgetSwitcher :: forall t m r e. StandardWidget t m r e => m () +widgetSwitcher = do + uiConfigD <- getAppState ^. mapping #uiConfig + D.el "div" . D.dyn_ $ uiConfigD <&> withUIConfig + where + withUIConfig uiConfig = do + let userWidgets = mkWidget <$> uiConfig ^. #viewList + buttons <- forM (widgets <> userWidgets) selectButton + listName <- R.holdDyn (fromMaybe ("No list", pass) (Seq.lookup 0 widgets)) (R.leftmost (toList buttons)) + D.el "div" $ D.dyn_ (snd <$> listName) + selectButton label = + (label <$) . D.domEvent D.Click . fst + <$> D.elClass' + "a" + "selector" + (D.text $ fst label) + mkWidget :: Widget -> (Text, m ()) + mkWidget SearchWidget = ("Search", D.text "Not implemented") + mkWidget (DefinitionElementWidget name definitionElement) = (name, definitionElementWidget NoContext definitionElement) + +filterInbox :: UTCTime -> TaskState -> Seq CalendarEvent -> Seq TaskInfos +filterInbox now tasks events = + Seq.reverse . Seq.sortOn (^. #modified) . fromList . toListOf (folded % filtered inInbox) $ tasks + where + scheduledEvents :: Set UUID + scheduledEvents = fromList $ toList $ mapMaybe (^? #_ListElement % #_TaskwarriorTask) <$> (^. #todoList % #entries) =<< events + inInbox :: TaskInfos -> Bool + inInbox taskInfos = + has (#tags % _Empty) taskInfos + && maybe True (now >=) (taskInfos ^. #wait) + && has (#status % #_Pending) taskInfos + && (not . any stillTodo . lookupTasks tasks) (taskInfos ^. #children) + && ( not + . any (`notElem` ["kategorie", "root"]) + . Set.unions + . view (mapping #tags) + $ lookupTasks tasks (taskInfos ^. #parents) + ) + && not (taskInfos ^. #blocked) + && not ((taskInfos ^. #uuid) `Set.member` scheduledEvents) + +getInboxTasks :: StandardWidget t m r e => m (D.Dynamic t (Seq TaskInfos)) +getInboxTasks = do + appState <- getAppState + timeDyn <- zonedTimeToUTC <<$>> getTime + let calendarEvents = appState ^. #calendarEvents + tasks <- getTasks + R.holdUniqDyn $ filterInbox <$> timeDyn <*> tasks <*> calendarEvents + +nextWidget :: StandardWidget t m r e => m () +nextWidget = do + inboxTasks <- getInboxTasks + unsortedTasks <- + fmap + ( filter + ( \task -> + not (Set.member "root" (task ^. #tags)) + && has (#partof % _Nothing) task + && has (#status % #_Pending) task + ) + . HashMap.elems + ) + <$> getTasks + D.dynText $ + (\x y -> if length x + length y > 0 then [i|There are #{length x} tasks in the inbox and #{length y} tasks unsorted.|] else "Nothing to do.") + <$> inboxTasks <*> unsortedTasks + inboxTaskDyn <- R.maybeDyn $ Seq.lookup 0 <$> inboxTasks + let decorateSortTask x = D.el "p" (D.text "Sort this task into the task tree:") *> taskTreeWidget x + decorateInboxTask x = D.el "p" $ do + D.text "Process this task from the inbox:" *> br + D.text "1. Does it need to be done?" *> br + D.text "2. Can you do it in under 2 minutes?" *> br + D.text "3. Should someone else do this?" *> br + D.text "4. Should you split this task into sub tasks?" *> br + D.text "5. On which tag list does it belong or when do you want to do it?" *> br + *> taskTreeWidget x + sortTask = do + taskDyn <- R.maybeDyn $ viaNonEmpty head <$> unsortedTasks + D.dyn_ (maybe pass decorateSortTask <$> taskDyn) + D.dyn_ (maybe sortTask decorateInboxTask <$> inboxTaskDyn) diff --git a/apps/kassandra/kassandra/src/Kassandra/ReflexUtil.hs b/apps/kassandra/kassandra/src/Kassandra/ReflexUtil.hs new file mode 100644 index 00000000..763accc3 --- /dev/null +++ b/apps/kassandra/kassandra/src/Kassandra/ReflexUtil.hs @@ -0,0 +1,84 @@ +{-# LANGUAGE BlockArguments #-} +{-# LANGUAGE PartialTypeSignatures #-} +{-# LANGUAGE ScopedTypeVariables #-} + +module Kassandra.ReflexUtil ( + smartSimpleList, + listWithGaps, + keyDynamic, +) where + +import qualified Data.Map as Map +import qualified Data.Patch.Map as Patch +--import qualified Data.Patch.MapWithMove as Patch +import qualified Data.Sequence as Seq +import qualified Reflex as R +import qualified Reflex.Dom as D + +{- | Renders a list of widgets depending on a Dynamic list of inputs. This will + call the widget constructor once per value in the list. + When the list changes, the widget will move and reuse all values that it can + so that it only needs to call the constructor again, when a new value (or a + second copy of a same value) appears in the list. +-} +smartSimpleList :: + forall t m v. + (R.Adjustable t m, R.PostBuild t m, Ord v, R.MonadHold t m, MonadFix m, D.NotReady t m) => + (v -> m ()) -> + R.Dynamic t (Seq v) -> + m () +smartSimpleList widget listElements = do + void $ R.simpleList (toList <$> listElements) \vDyn -> do + u <- R.holdUniqDyn vDyn + D.dyn_ . fmap widget $ u + --postBuild <- R.getPostBuild + --keyMap <- R.holdUniqDyn $ Seq.foldMapWithIndex (curry one) <$> listElements + --let keyMapChange = + --R.attachWith + --((Newtype.under @(Map Int (Patch.NodeInfo Int v)) fixPatchMap .) . Patch.patchThatChangesMap) + --(R.current keyMap) + --(R.updated keyMap) + --initialKeyMap = Patch.patchMapWithMoveInsertAll <$> R.tag (R.current keyMap) postBuild + --keyMapEvents = keyMapChange <> initialKeyMap + --void $ R.mapMapWithAdjustWithMove (const widget) mempty keyMapEvents + +-- | A workaround for a bug in patchThatChangesMap in patch 0.0.3.2. +--fixPatchMap :: Map Int (Patch.NodeInfo Int v) -> Map Int (Patch.NodeInfo Int v) +--fixPatchMap inputMap = appEndo setMoves . fmap (Patch.nodeInfoSetTo Nothing) $ inputMap +-- where +-- setMoves = Map.foldMapWithKey f inputMap +-- f to' (Patch.NodeInfo (Patch.From_Move from) _) = Endo $ Map.adjust (Patch.nodeInfoSetTo (Just to')) from +-- f _ _ = mempty + +listWithGaps :: + (R.Adjustable t m, R.PostBuild t m, R.MonadHold t m, MonadFix m, Ord v, D.NotReady t m) => + (v -> m ()) -> + (R.Dynamic t (Maybe v, Maybe v) -> m ()) -> + R.Dynamic t (Seq v) -> + m () +listWithGaps widget gapWidget listD = do + smartSimpleList elementWidget listD + lastElementD <- R.holdUniqDyn $ (,Nothing) . lastOf folded <$> listD + gapWidget lastElementD + where + elementWidget currentElement = do + elementPair <- R.holdUniqDyn $ (,Just currentElement) . Map.lookup currentElement <$> prevElementsD + gapWidget elementPair + widget currentElement + prevElementsD = (\xs -> Map.unions . fmap one $ Seq.zip (Seq.drop 1 xs) xs) <$> listD + +keyDynamic :: + forall t k v. + (R.Reflex t, Ord k) => + R.Incremental t (Patch.PatchMap k v) -> + k -> + R.Dynamic t (Maybe v) +keyDynamic incremental key = + R.incrementalToDynamic + . R.unsafeMapIncremental mapMap mapPatchMap + $ incremental + where + mapMap :: Map k v -> Maybe v + mapMap = Map.lookup key + mapPatchMap :: Patch.PatchMap k v -> Identity (Maybe v) + mapPatchMap = Identity . join . Map.lookup key . Patch.unPatchMap diff --git a/apps/kassandra/kassandra/src/Kassandra/RemoteBackendWidget.hs b/apps/kassandra/kassandra/src/Kassandra/RemoteBackendWidget.hs new file mode 100644 index 00000000..63c793dd --- /dev/null +++ b/apps/kassandra/kassandra/src/Kassandra/RemoteBackendWidget.hs @@ -0,0 +1,167 @@ +module Kassandra.RemoteBackendWidget ( + remoteBackendWidget, + webClientSocket, + CloseEvent (..), +) where + +import Data.Aeson (decode, encode) +import Data.Map (elems, insert) +import Data.Text (stripPrefix) +import JSDOM (currentWindowUnchecked) +import JSDOM.Custom.Window (getLocalStorage) +import JSDOM.Generated.Storage (Storage, getItem, setItem) +import Kassandra.Api ( + SocketMessage, + SocketRequest (AllTasks), + ) +import Kassandra.BaseWidgets (button, stateWidget) +import Kassandra.Config ( + PasswordConfig (..), + RemoteBackend (..), + ) +import Kassandra.State ( + ClientSocket, + StateProvider, + makeStateProvider, + ) +import Kassandra.Types (WidgetJSM) +import Language.Javascript.JSaddle (liftJSM) +import qualified Reflex as R +import qualified Reflex.Dom as D +import Relude.Extra.Newtype (un, wrap) +import System.Process (readCreateProcess, shell) + +data Connecting = LoggedOut | LoggedIn deriving (Show, Read) + +newtype CloseEvent t = CloseEvent (R.Event t ()) deriving newtype (Semigroup, Monoid) + +urlKey, userKey, passwordKey, loginStateKey :: String +urlKey = "Url" +userKey = "User" +passwordKey = "Password" +loginStateKey = "LoginState" + +remoteBackendWidget :: + forall t m. + WidgetJSM t m => + CloseEvent t -> + Maybe (RemoteBackend PasswordConfig) -> + m (R.Dynamic t (Maybe (StateProvider t m))) +remoteBackendWidget closeEvent mayBackend = D.divClass "remoteBackend" $ do + backendDyn <- maybe inputBackend getPassword mayBackend + responseEvent <- + D.dyn + (withBackend (closeEvent <> wrap (() <$ R.updated backendDyn)) <$> backendDyn) + D.holdDyn Nothing responseEvent + where + getPassword :: RemoteBackend PasswordConfig -> m (R.Dynamic t (Maybe (RemoteBackend Text))) + getPassword RemoteBackend{url, user, password} = do + fmap (pure . RemoteBackend url user) <$> case password of + Password plain -> pure (pure plain) + PasswordCommand command -> pure . Text <$> liftIO (readCreateProcess (shell $ toString command) "") + Prompt -> do + D.text [i|Enter password for #{user} on #{url}:|] + storage <- getStorage + initialPassword <- fromMaybe "" <$> getItem storage passwordKey + passwordInput <- textInput True initialPassword + let sendEvent = R.tag (inputValue passwordInput) (D.keypress D.Enter passwordInput) + R.performEvent_ $ sendEvent <&> setItem storage ([i|PasswordFor#{user}On#{url}|] :: String) . toString + R.holdDyn "" sendEvent + inputBackend :: m (R.Dynamic t (Maybe (RemoteBackend Text))) + inputBackend = do + protocol <- D.getLocationProtocol + host <- D.getLocationHost + let defaultUrl = protocol <> "//" <> host + defaultUser = "" + defaultPassword = "" + storage <- getStorage + initialUrl <- fromMaybe defaultUrl <$> getItem storage urlKey + initialUser <- fromMaybe defaultUser <$> getItem storage userKey + initialPassword <- fromMaybe defaultPassword <$> getItem storage passwordKey + initialState <- fromMaybe LoggedOut . (readMaybe =<<) <$> getItem storage loginStateKey + stateEvent <- stateWidget (initialState, initialUrl, initialUser, initialPassword) stateTransition + R.performEvent_ $ + stateEvent <&> \(loginState, url, user, password) -> do + setItem storage loginStateKey (show loginState :: String) + setItem storage urlKey url + setItem storage userKey user + setItem storage passwordKey password + fmap backendFromState <$> R.holdDyn (initialState, initialUrl, initialUser, initialPassword) stateEvent + stateTransition (loginState, url, user, password) = case loginState of + LoggedOut -> D.divClass "loginDialog" $ do + D.text "Host:" + urlInput <- textInput False url + D.el "br" pass + D.text "User:" + userNameInput <- textInput False user + D.el "br" pass + D.text "Password:" + passwordInput <- textInput True password + D.el "br" pass + saveButton <- button "selector" $ D.text "Login" + let saveEvent = + liftA3 + (LoggedIn,,,) + (inputValue urlInput) + (inputValue userNameInput) + (inputValue passwordInput) + R.<@ fold (saveButton : (D.keypress D.Enter <$> [urlInput, userNameInput, passwordInput])) + pure (saveEvent, saveEvent) + LoggedIn -> do + D.text [i|#{user} @ #{url}|] + logoutEvent <- + ((LoggedOut, url, user, password) <$) + <$> button "selector" (D.text "Logout") + pure (logoutEvent, logoutEvent) + backendFromState (LoggedIn, url, user, password) = Just $ RemoteBackend url user password + backendFromState (LoggedOut, _, _, _) = Nothing + withBackend :: CloseEvent t -> Maybe (RemoteBackend Text) -> m (Maybe (StateProvider t m)) + withBackend innerCloseEvent = traverse (fmap makeStateProvider . webClientSocket innerCloseEvent) + -- TODO: Get UI Config from Server + textInput hidden defaultValue = + D.inputElement $ + D.def + & lensVL D.inputElementConfig_initialValue + .~ defaultValue + & lensVL (D.inputElementConfig_elementConfig . D.elementConfig_initialAttributes) + .~ if hidden then "type" D.=: "password" else mempty + inputValue = R.current . D._inputElement_value + +data WebSocketState = WebSocketError Text | Connecting deriving stock (Show) + +getStorage :: WidgetJSM t m => m Storage +getStorage = getLocalStorage =<< currentWindowUnchecked + +webClientSocket :: + WidgetJSM t m => CloseEvent t -> RemoteBackend Text -> m (ClientSocket t m) +webClientSocket closeEvent backend@RemoteBackend{url, user, password} = do + refreshEvent <- button "selector" $ D.text "Refresh Tasks" + let wsUrl = maybe "ws://localhost:8000" ("ws" <>) $ stripPrefix "http" url -- TODO: Warn user about missing http + socketString = [i|#{wsUrl}/socket?username=#{user}&password=#{password}|] + clientSocket socketRequestEvent = do + let socketConfig = + D.def + & (lensVL D.webSocketConfig_send .~ (toList <$> (socketRequestEvent <> (one AllTasks <$ refreshEvent)))) + . (lensVL D.webSocketConfig_reconnect .~ True) + . (lensVL D.webSocketConfig_close .~ ((3000, "Client unloaded websocket.") <$ un closeEvent)) + storage <- getStorage + socket <- D.jsonWebSocket @SocketRequest @SocketMessage socketString socketConfig + let messages = R.fmapMaybe id $ socket ^. lensVL D.webSocket_recv + err = [i|Connection to kassandra server not possible. Check network connection, server url and credentials. #{backend}|] + close = Just err <$ socket ^. lensVL D.webSocket_close + open = Nothing <$ socket ^. lensVL D.webSocket_open + messageParseFail = maybe (Just "Failed to parse SocketMessage JSON") (const Nothing) <$> socket ^. lensVL D.webSocket_recv + nextStateEvent = R.leftmost [messageParseFail, close, open] + D.dynText . fmap (fromMaybe "") =<< R.holdDyn (Just "Websocket Connecting ...") nextStateEvent + let taskUpdates = (^? #_TaskUpdates) <$?> messages + mapKey = [i|TaskMap#{wsUrl}#{user}|] :: String + foldTasksToMap tasks currentMap = foldr (\task theMap -> insert (task ^. #uuid) task theMap) currentMap tasks + asyncSaveStorage = void . liftJSM . D.forkJSM . setItem storage mapKey . decodeUtf8 @Text . encode + taskMap :: Map UUID Task <- maybeToMonoid . (decode . encodeUtf8 @Text =<<) <$> getItem storage mapKey + tasksToSave <- R.foldDyn foldTasksToMap taskMap taskUpdates + -- TODO: Saving the whole Map everytime is probably quite inefficient, we should try to reduce writes by a smarter saving scheme + void . R.performEventAsync $ const . asyncSaveStorage <$> R.updated tasksToSave + ev <- R.getPostBuild + let cachedTasks = R.fmapMaybe (nonEmptySeq . fromList) $ elems taskMap <$ ev + pure . pure $ R.leftmost [messages, (#_TaskUpdates #) <$> cachedTasks] + pure clientSocket diff --git a/apps/kassandra/kassandra/src/Kassandra/SelectorWidget.hs b/apps/kassandra/kassandra/src/Kassandra/SelectorWidget.hs new file mode 100644 index 00000000..f8decd12 --- /dev/null +++ b/apps/kassandra/kassandra/src/Kassandra/SelectorWidget.hs @@ -0,0 +1,18 @@ +module Kassandra.SelectorWidget ( + backendSelector, +) where + +import Kassandra.Config (NamedBackend (..)) +import Kassandra.Types (Widget) +import qualified Reflex as R +import qualified Reflex.Dom as D + +backendSelector :: + Widget t m => NonEmpty (NamedBackend a) -> m (R.Dynamic t (NamedBackend a)) +backendSelector backends = D.el "div" $ do + buttons <- forM backends $ \backend -> do + fmap ((backend <$) . D.domEvent D.Click . fst) + . D.elClass' "a" "selector" + . D.text + $ name backend + R.holdDyn (head backends) $ R.leftmost (toList buttons) diff --git a/apps/kassandra/kassandra/src/Kassandra/Sorting.hs b/apps/kassandra/kassandra/src/Kassandra/Sorting.hs new file mode 100644 index 00000000..3cc5e5d2 --- /dev/null +++ b/apps/kassandra/kassandra/src/Kassandra/Sorting.hs @@ -0,0 +1,182 @@ +module Kassandra.Sorting ( + sortTasks, + saveSorting, + SortPosition (SortPosition), + SortMode (SortModePartof, SortModeTag), +) where + +import qualified Data.Aeson as Aeson +import Data.Scientific (toRealFloat) +import Data.Set (member) +import Kassandra.Types (TaskInfos) +import qualified Reflex as R +import Relude.Extra.Foldable1 (maximum1) +import qualified Taskwarrior.Task as Task +import qualified Data.Sequence as Seq + +data SortMode = SortModePartof UUID | SortModeTag Task.Tag + deriving stock (Show, Eq, Ord, Generic) +makePrismLabels ''SortMode + +data SortState = HasSortPos Double | WillWrite {iprev :: Double, dprev :: Int, inext :: Double, dnext :: Int} + deriving stock (Eq, Show, Ord, Read, Generic) +makePrismLabels ''SortState + +declareFieldLabels + [d| + data SortPosition t = SortPosition + { mode :: R.Behavior t SortMode + , list :: R.Behavior t (Seq Task) + , before :: R.Behavior t (Maybe UUID) + } + |] + +sortTasks :: SortMode -> Seq TaskInfos -> Seq TaskInfos +sortTasks mode = Seq.sortOn (getSortOrder mode . (^. #task)) + +getSortOrder :: SortMode -> Task -> Maybe Double +getSortOrder mode = valToNumber <=< (^. #uda % at (sortFieldName mode)) + +valToNumber :: Aeson.Value -> Maybe Double +valToNumber = \case + Aeson.Number a -> _Just # toRealFloat a + Aeson.String a -> readMaybe $ a ^. unpacked + _ -> Nothing + +sortFieldName :: SortMode -> Text +sortFieldName = \case + SortModeTag tag -> "kassandra_tag_pos_" <> tag + SortModePartof _ -> "kassandra_partof_pos" + +setSortOrder :: SortMode -> Double -> Task -> Task +setSortOrder mode val = #uda %~ at (sortFieldName mode) ?~ Aeson.toJSON val + +taskInList :: SortMode -> Task -> Bool +taskInList (SortModePartof uuid) = (Just uuid ==) . (^. #partof) +taskInList (SortModeTag tag) = member tag . Task.tags + +insertInList :: SortMode -> Task -> Task +insertInList (SortModePartof uuid) = #partof ?~ uuid +insertInList (SortModeTag tag) = #tags %~ addTag + where + addTag tags + | tag `member` tags = tags + | otherwise = tags <> one tag + +unSetSortOrder :: SortMode -> Task -> Task +unSetSortOrder mode = #uda %~ sans (sortFieldName mode) + +-- ! Returns a list of tasks for which the UDA attributes need to be changed to reflect the order of the given list. +sortingChanges :: SortMode -> Seq Task -> Seq Task +sortingChanges mode list = + let addState = addSortState (getSortOrder mode) + assureSort delta = + applyUntil + (addState . unSetWorstUnsorted (unSetSortOrder mode) delta) + (tasksSorted delta) + sortedList = assureSort 0 $ addState list + finalList + | tasksSorted minDist sortedList = sortedList + | otherwise = assureSort minTouchedDist sortedList + getWrite (task, sortState) + | has #_WillWrite sortState || not (taskInList mode task) = + Just . setSortOrder mode (newValue sortState) . insertInList mode $ task + | otherwise = + Nothing + in mapMaybe getWrite finalList + +applyUntil :: (a -> a) -> (a -> Bool) -> a -> a +applyUntil f condition x + | condition x = x + | otherwise = applyUntil f condition (f x) + +minOrder, maxOrder, minDist, minTouchedDist :: Double +minOrder = -1 +maxOrder = - minOrder +minDist = 10 ** (-6) +minTouchedDist = 10 ** (-3) + +tasksSorted :: Show a => Double -> Seq (a, SortState) -> Bool +tasksSorted = isSortedOn (newValue . (^. _2)) +isSortedOn :: Show a => (a -> Double) -> Double -> Seq a -> Bool +isSortedOn f delta = \case + IsEmpty -> True + IsNonEmpty (_ :<|| IsEmpty) -> True + IsNonEmpty (x :<|| IsNonEmpty (y :<|| ys)) -> f x + delta < f y && isSortedOn f delta (y <| ys) + +unSetWorstUnsorted :: (a -> a) -> Double -> Seq (a, SortState) -> Seq a +unSetWorstUnsorted _ _ IsEmpty = mempty +unSetWorstUnsorted unSet delta (IsNonEmpty (x :<|| xs)) + | ((fst <$>) -> fine, (fst <$>) -> IsNonEmpty (a :<|| alsoFine)) <- + Seq.breakl + ((worst ==) . snd) + (toSeq badnesses) = + fine <> (unSet a <| alsoFine) + | otherwise = + error "Assumed wrong invariant in unSetWorstUnsorted" -- The list of badnesses has to contain its maximum + where + badnesses = go mempty (x :<|| xs) <&> \(a, _, badness) -> (a, badness) + worst = maximum1 $ snd <$> badnesses + go :: Seq Double -> NESeq (a, SortState) -> NESeq (a, Double, Int) + go before ((a, s@(newValue -> value)) :<|| ys) + | has #_WillWrite s = (a, value, 0) :<|| rest + | otherwise = (a, value, foundBefore + foundAfter) :<|| rest + where + foundBefore = length $ filter (value - delta <=) before + (foundAfter, rest) = maybe (0, mempty) countThroughRest (nonEmptySeq ys) + countThroughRest list = + (length $ filter (\(_, int, _) -> value + delta >= int) zs, zs) + where + zs = toSeq (go (value <| before) list) + +newValue :: SortState -> Double +newValue (HasSortPos x) = x +newValue (WillWrite iprev (fromIntegral -> dprev) inext (fromIntegral -> dnext)) + | dprev + dnext == 0 = iprev + | otherwise = iprev + ((inext - iprev) * dprev / (dprev + dnext)) + +sortStateNext :: SortState -> (Double, Int) +sortStateNext (HasSortPos a) = (a, 0) +sortStateNext (WillWrite _ _ int d) = (int, d) + +addSortState :: forall a. (a -> Maybe Double) -> Seq a -> Seq (a, SortState) +addSortState f = go (minOrder, 0) + where + go :: (Double, Int) -> Seq a -> Seq (a, SortState) + go (iprev, dprev) list + | IsEmpty <- list = mempty + | IsNonEmpty (x :<|| xs) <- list + , Just int <- f x = + (x, HasSortPos int) <| go (int, 0) xs + | IsNonEmpty (x :<|| xs) <- list + , next@(IsNonEmpty ((_, sortStateNext -> (inext, dnext)) :<|| _)) <- + go + (iprev, dprev + 1) + xs = + (x, WillWrite iprev (dprev + 1) inext (dnext + 1)) <| next + | IsNonEmpty (x :<|| _) <- list = + one (x, WillWrite iprev (dprev + 1) maxOrder 1) + +insertBefore :: Seq Task -> Seq Task -> Maybe UUID -> Seq Task +insertBefore list toInsert = \case + Just uuid -> + let (front, back) = Seq.breakl ((== uuid) . (^. #uuid)) cleanList + in front <> toInsert <> back + Nothing -> cleanList <> toInsert + where + cleanList = filter ((`notElem` (toInsert ^. mapping #uuid)) . (^. #uuid)) list + +type InsertEvent t = R.Event t (NESeq Task, Maybe UUID) + +saveSorting :: + R.Reflex t => + R.Behavior t SortMode -> + R.Behavior t (Seq Task) -> + InsertEvent t -> + R.Event t (NESeq Task) +saveSorting modeB listB = + R.fmapMaybe nonEmptySeq + . R.attachWith attach ((,) <$> modeB <*> listB) + where + attach (mode, list) (toSeq -> tasks, before) = + sortingChanges mode $ insertBefore list tasks before diff --git a/apps/kassandra/kassandra/src/Kassandra/State.hs b/apps/kassandra/kassandra/src/Kassandra/State.hs new file mode 100644 index 00000000..94b2988a --- /dev/null +++ b/apps/kassandra/kassandra/src/Kassandra/State.hs @@ -0,0 +1,104 @@ +module Kassandra.State ( + makeStateProvider, + StateProvider, + ClientSocket, + DataState (..), +) where + +import qualified Data.HashMap.Strict as HashMap +import qualified Data.Sequence as Seq +import Kassandra.Api (SocketMessage (..), SocketRequest (..)) +import Kassandra.Calendar +import Kassandra.Config (UIConfig) +import Kassandra.Types ( + DataChange, + TaskInfos (..), + TaskState, + WidgetIO, + ) +import qualified Reflex as R +import qualified Reflex.Dom as D +import Taskwarrior.IO (createTask) + +getParents :: HashMap UUID Task -> UUID -> Seq UUID +getParents tasks = go mempty (\uuid -> (^. #partof) =<< tasks ^. at uuid) + where + go :: (Eq a, Show a) => Seq a -> (a -> Maybe a) -> a -> Seq a + go accu f x + | x `elem` accu = mempty + | Just next <- f x = next <| go (x <| accu) f next + | otherwise = mempty + +data DataState = DataState + { taskState :: TaskState + , uiConfig :: UIConfig + , calendarData :: Seq CalendarEvent + } +makeLabels ''DataState + +type StateProvider t m = R.Event t (NESeq DataChange) -> m (R.Dynamic t DataState) + +type ClientSocket t m = R.Event t (NESeq SocketRequest) -> m (R.Dynamic t (R.Event t SocketMessage)) + +makeStateProvider :: forall t m. WidgetIO t m => ClientSocket t m -> StateProvider t m +makeStateProvider clientSocket dataChangeEvents = do + let fanEvent :: (b -> Maybe a) -> R.Event t (NESeq b) -> R.Event t (NESeq a) + fanEvent decons = R.fmapMaybe (nonEmptySeq . mapMaybe decons . toSeq) + createTaskEvent = fanEvent (^? #_CreateTask) dataChangeEvents + changeTaskEvent = fanEvent (^? #_ChangeTask) dataChangeEvents + setListEvent = fanEvent (^? #_SetEventList) dataChangeEvents + changesFromCreateEvents <- createToChangeEvent createTaskEvent + let localChanges = changeTaskEvent <> changesFromCreateEvents + rec remoteChanges <- R.switchDyn <$> clientSocket (fold eventsToSend) + let connectedEvent = (^? #_ConnectionEstablished) <$?> remoteChanges + eventsToSend = + [ one . ChangeTasks <$> localChanges + , AllTasks :<|| fromList [CalenderRequest, UIConfigRequest] <$ connectedEvent + , uncurry SetCalendarList <<$>> setListEvent + ] + let errorEvent = (^? #_SocketError) <$?> remoteChanges + calendarData <- R.holdDyn mempty $ Seq.sortBy sortEvents <$> ((^? #_CalendarEvents) <$?> remoteChanges) + uiConfig <- R.holdDyn D.def $ (^? #_UIConfigResponse) <$?> remoteChanges + D.dynText =<< R.foldDyn (<>) "" errorEvent + tasksStateDyn <- buildTaskInfosMap <<$>> holdTasks (localChanges <> ((^? #_TaskUpdates) <$?> remoteChanges)) + pure (DataState <$> tasksStateDyn <*> uiConfig <*> calendarData) + +createToChangeEvent :: WidgetIO t m => D.Event t (NESeq (Text, Task -> Task)) -> m (D.Event t (NESeq Task)) +createToChangeEvent = R.performEvent . fmap (liftIO . mapM (\(desc, properties) -> properties <$> createTask desc)) + +holdTasks :: WidgetIO t m => R.Event t (NESeq Task) -> m (R.Dynamic t (HashMap UUID Task)) +holdTasks = R.foldDyn foldTasks mempty + +foldTasks :: Foldable t => t Task -> HashMap UUID Task -> HashMap UUID Task +foldTasks = flip (foldr (\task -> HashMap.insert (task ^. #uuid) task)) + +buildChildrenMap :: HashMap a Task -> HashMap UUID (Seq a) +buildChildrenMap = + HashMap.fromListWith (<>) + . mapMaybe (\(uuid, task) -> (,pure uuid) <$> task ^. #partof) + . HashMap.toList + +buildDependenciesMap :: HashMap a Task -> HashMap UUID (Seq a) +buildDependenciesMap = HashMap.fromListWith (<>) . (HashMap.toList >=> \(uuid, task) -> (,pure uuid) <$> toList (task ^. #depends)) + +buildTaskInfosMap :: HashMap UUID Task -> TaskState +buildTaskInfosMap tasks = + HashMap.mapWithKey foldTaskMap tasks + where + foldTaskMap uuid task = + TaskInfos + { task = task + , children = HashMap.lookupDefault mempty uuid childrenMap + , parents = getParentTasks uuid + , revDepends = HashMap.lookupDefault mempty uuid dependenciesMap + , blocked = isBlockedTask task + } + isBlockedTask = isBlocked tasks + getParentTasks = getParents tasks + dependenciesMap = buildDependenciesMap tasks + childrenMap = buildChildrenMap tasks + +isBlocked :: HashMap UUID Task -> Task -> Bool +isBlocked tasks task = any isActive . mapMaybe (`HashMap.lookup` tasks) . toList $ task ^. #depends + where + isActive t = has (#status % #_Pending) t diff --git a/apps/kassandra/kassandra/src/Kassandra/TaskWidget.hs b/apps/kassandra/kassandra/src/Kassandra/TaskWidget.hs new file mode 100644 index 00000000..39e20ef5 --- /dev/null +++ b/apps/kassandra/kassandra/src/Kassandra/TaskWidget.hs @@ -0,0 +1,409 @@ +module Kassandra.TaskWidget ( + taskTreeWidget, + taskList, + uuidWidget, +) where + +import qualified Data.HashSet as HashSet +import qualified Data.Sequence as Seq +import qualified Data.Sequence.NonEmpty as NESeq +import qualified Data.Set as Set +import qualified Data.Text as Text +import Kassandra.BaseWidgets ( + button, + icon, br + ) +import Kassandra.Config (DefinitionElement) +import Kassandra.Debug ( + Severity (..), + log, + ) +import Kassandra.DragAndDrop ( + childDropArea, + taskDropArea, + tellSelected, + ) +import Kassandra.ReflexUtil (listWithGaps) +import Kassandra.Sorting ( + SortMode (SortModePartof), + SortPosition (SortPosition), + sortTasks, + ) +import Kassandra.TextEditWidget ( + createTextWidget, + lineWidget, + ) +import Kassandra.TimeWidgets (dateSelectionWidget) +import Kassandra.Types ( + AppState, + Have, + StandardWidget, + TaskInfos, + TaskTreeState, + TaskTreeStateChange, + TaskTreeWidget, + ToggleEvent (ToggleEvent), + getAppState, + getExpandedTasks, + getIsExpanded, + getSelectState, + getTime, + ) +import Kassandra.Util (lookupTaskM, lookupTasksDynM, lookupTasksM, stillTodo, tellNewTask, tellTask, tellToggle) +import qualified Reflex as R +import Reflex.Dom ((=:)) +import qualified Reflex.Dom as D +import qualified Taskwarrior.Status as Status +import Taskwarrior.UDA (UDA) + +type TaskWidget t m r e = (TaskTreeWidget t m r e, HaveTask m r) +type HaveTask m r = Have m r TaskInfos + +instance LabelOptic "taskInfos" A_Lens (a, TaskInfos) (a, TaskInfos) TaskInfos TaskInfos where + labelOptic = _2 + +getTaskInfos :: HaveTask m r => m TaskInfos +getTaskInfos = ask ^. mapping typed + +getChildren :: TaskWidget t m r e => m (R.Dynamic t (Seq TaskInfos)) +getChildren = getTaskInfos ^. mapping #children >>= lookupTasksM + +taskTreeWidget :: + forall t m r e. StandardWidget t m r e => R.Dynamic t TaskInfos -> m () +taskTreeWidget taskInfosD = do + log Debug "Creating Tasktree Widget" + (appState :: AppState t) <- getAppState + rec treeState <- + R.foldDyn + ( flip $ + foldr + ( \case + ToggleEvent uuid False -> HashSet.delete uuid + ToggleEvent uuid True -> HashSet.insert uuid + ) :: + NESeq ToggleEvent -> HashSet UUID -> HashSet UUID + ) + mempty + treeStateChanges + (_, events :: R.Event t (NESeq TaskTreeStateChange)) <- + R.runEventWriterT $ + runReaderT (taskWidget taskInfosD) (appState, treeState) + let (appStateChanges, treeStateChanges) = + R.fanThese $ partitionEithersNESeq <$> events + R.tellEvent (fmap (_Typed #) <$> appStateChanges) + +taskWidget :: + forall t m r e. (TaskTreeWidget t m r e) => R.Dynamic t TaskInfos -> m () +taskWidget taskInfos' = D.divClass "task" $ do + taskInfosD <- R.holdUniqDyn taskInfos' + appState <- getAppState + treeState <- ask ^. mapping typed + D.dyn_ $ taskInfosD <&> \taskInfos -> runReaderT widgets (appState, taskInfos, treeState) + childrenWidget taskInfosD + where + widgets :: ReaderT (AppState t, TaskInfos, TaskTreeState t) m () + widgets = D.divClass "uppertask" $ do + D.divClass "statusWrapper" statusWidget + D.divClass "righttask" $ do + collapseButton + descriptionWidget + tagsWidget + waitWidget + dueWidget + pathWidget + parentButton + dependenciesWidget + addChildWidget + deleteButton + completedWidget + selectWidget + dropChildWidget + +pathWidget :: TaskWidget t m r e => m () +pathWidget = do + parents <- getTaskInfos ^. mapping #parents >>= lookupTasksM ^. mapping (mapping (mapping (mapping #description))) + D.dyn_ $ flip whenJust showPath . nonEmptySeq <$> parents + where + showPath :: TaskWidget t m r e => NESeq Text -> m () + showPath parents = D.elClass "span" "parentPath" $ do + br + makePath parents + +makePath :: (TaskWidget t m r e) => NESeq Text -> m () +makePath = D.elClass "span" "path" . D.text . Text.intercalate " ā‰« " . toList . NESeq.reverse + +dependenciesWidget :: (TaskWidget t m r e) => m () +dependenciesWidget = do + taskInfos <- getTaskInfos + revDepends <- filter stillTodo <<$>> lookupTasksM (taskInfos ^. #revDepends) + depends <- filter stillTodo <<$>> (lookupTasksM . toList) (taskInfos ^. #depends) + D.dyn_ $ + whenNotNull <$> depends + <*> pure + ( \ds -> do + br + D.text "dependencies: " + forM_ ds $ \d -> do + makeOwnPath d + deleteEvent <- button "edit" $ icon "" "delete" + tellTask $ removeDep (taskInfos ^. #task) d <$ deleteEvent + br + ) + D.dyn_ $ + whenJust . nonEmptySeq <$> revDepends + <*> pure + ( \rds -> do + br + D.text "reverse dependencies: " + forM_ rds $ \rd -> do + makeOwnPath rd + deleteEvent <- button "edit" $ icon "" "delete" + tellTask $ removeDep (rd ^. #task) taskInfos <$ deleteEvent + br + ) + where + removeDep :: + ( Is k1 A_Getter + , Is k2 A_Setter + , LabelOptic "depends" k2 s1 t (Set UUID) (Set UUID) + , LabelOptic "uuid" k1 s2 s2 UUID UUID + ) => + s1 -> + s2 -> + t + removeDep task dependency = + #depends %~ Set.filter (dependency ^. #uuid /=) $ task + +makeOwnPath :: (TaskWidget t m r e) => TaskInfos -> m () +makeOwnPath task = + D.dyn_ + . fmap (makePath . (\ps -> task ^. #description :<|| ps)) + =<< lookupTasksM (task ^. #parents) ^. mapping (mapping (mapping #description)) + +dropChildWidget :: (TaskWidget t m r e) => m () +dropChildWidget = do + taskInfos <- getTaskInfos + childrenD <- getChildren + showIcon <- fmap not <$> getIsExpanded (taskInfos ^. #uuid) + D.dyn_ $ + when <$> showIcon + <*> pure + ( childDropArea + ( SortPosition + (SortModePartof <$> taskInfos ^. #uuid % to R.constant) + (childrenD ^. mapping (mapping #task) % #current) + (R.constant Nothing) + ) + ( R.constDyn + (taskInfos ^. #uuid <| taskInfos ^. #parents <> taskInfos ^. #children) + ) + $ icon "dropHere" "move_to_inbox" + ) + taskDropArea + (taskInfos ^. #uuid % to (R.constDyn . one)) + (icon "dropHere plusOne" "block") + $ fmap + ( \dependencies -> + one $ + #depends + %~ Set.union (Set.fromList $ toList $ (^. #uuid) <$> dependencies) + $ taskInfos + ^. #task + ) + taskDropArea + (taskInfos ^. #uuid % to (R.constDyn . one)) + (icon "dropHere plusTwo" "schedule") + $ fmap (fmap ((#depends %~ Set.insert (taskInfos ^. #uuid)) . (^. #task))) + +tagsWidget :: forall t m r e. TaskWidget t m r e => m () +tagsWidget = do + task <- getTaskInfos ^. mapping #task + forM_ (task ^. #tags) $ \tag -> D.elClass "span" "tag" $ do + D.text tag + deleteEvent <- button "edit" $ icon "" "delete" + tellTask $ (#tags %~ Set.filter (tag /=) $ task) <$ deleteEvent + tagEvent <- createTextWidget . button "edit" $ icon "" "add_box" + tellTask $ (\tag -> #tags %~ Set.insert tag $ task) <$> tagEvent + +getNewUDA :: forall t m r e. TaskWidget t m r e => m UDA +getNewUDA = one . ("partof",) . toJSON <$> getTaskInfos ^. mapping #uuid + +addChildWidget :: TaskWidget t m r e => m () +addChildWidget = do + descriptionEvent <- createTextWidget . button "edit" $ icon "" "add_task" + newUDA <- getNewUDA + tellNewTask $ (,#uda .~ newUDA) <$> descriptionEvent + +childrenWidget :: forall t m r e. TaskTreeWidget t m r e => R.Dynamic t TaskInfos -> m () +childrenWidget taskInfosD = do + expandedTasks <- getExpandedTasks + showChildren <- + R.holdUniqDyn $ + R.zipDynWith HashSet.member (taskInfosD ^. mapping #uuid) expandedTasks + D.dyn_ $ showOptional <$> showChildren + where + showOptional :: Bool -> m () + showOptional x = when x $ do + children <- + R.holdUniqDyn . fmap (filter stillTodo) =<< lookupTasksDynM + =<< R.holdUniqDyn + (taskInfosD ^. mapping #children) + let sortModeD = SortModePartof <$> taskInfosD ^. mapping #uuid + blacklist <- + R.holdUniqDyn $ + liftA2 (<|) (taskInfosD ^. mapping #uuid) (taskInfosD ^. mapping #parents) + sortedList <- R.holdUniqDyn $ sortTasks <$> sortModeD <*> children + D.divClass "children" $ + taskList (sortModeD ^. #current) sortedList blacklist taskWidget + + +taskList :: + StandardWidget t m r e => + R.Behavior t SortMode -> + R.Dynamic t (Seq TaskInfos) -> + R.Dynamic t (Seq UUID) -> + (R.Dynamic t TaskInfos -> m ()) -> + m () +taskList mode tasksD blacklistD elementWidget = + listWithGaps (uuidWidget elementWidget . pure) gapWidget uuidsD + where + gapWidget pairD = + childDropArea + (partialSortPosition (snd <$> R.current pairD)) + ((maybesToSeq <$> pairD) <> blacklistD) + $ icon "dropHere above" "forward" + maybesToSeq (a, b) = fromList (toList a <> toList b) + partialSortPosition = SortPosition mode (tasksD ^. mapping (mapping #task) % #current) + uuidsD = tasksD ^. mapping (mapping #uuid) + +uuidWidget :: StandardWidget t m r e => (R.Dynamic t TaskInfos -> m ()) -> R.Dynamic t UUID -> m () +uuidWidget widget uuid = do + maybeCurrentTaskD <- R.maybeDyn =<< R.holdUniqDyn =<< lookupTaskM uuid + D.dyn_ $ + maybe + (D.dynText $ (\u -> [i|Task #{u} not found.|]) <$> uuid) + widget + <$> maybeCurrentTaskD + +waitWidget :: forall t m r e. TaskWidget t m r e => m () +waitWidget = do + task <- getTaskInfos ^. mapping #task + event <- getTaskInfos >>= ((^. #wait) >>> dateSelectionWidget "wait") + tellTask $ flip (#wait .~) task <$> event + +dueWidget :: TaskWidget t m r e => m () +dueWidget = do + task <- getTaskInfos ^. mapping #task + event <- dateSelectionWidget "due" $ task ^. #due + tellTask $ flip (#due .~) task <$> event + +selectWidget :: TaskWidget t m r e => m () +selectWidget = do + uuid <- getTaskInfos ^. mapping (#task % #uuid) + (dragEl, _) <- D.elClass' "span" "button" $ icon "" "filter_list" + selectStateB <- toggleContainUUID uuid <<$>> R.current <$> getSelectState + tellSelected $ R.tag selectStateB (D.domEvent D.Click dragEl) + where + toggleContainUUID :: UUID -> Seq DefinitionElement -> Seq DefinitionElement + toggleContainUUID ((#_ListElement % #_TaskwarriorTask #) -> entry) selectedTasks = + Seq.findIndexL (== entry) selectedTasks & maybe (selectedTasks |> entry) (`Seq.deleteAt` selectedTasks) + +descriptionWidget :: TaskWidget t m r e => m () +descriptionWidget = do + task <- getTaskInfos ^. mapping #task + event <- lineWidget $ task ^. #description + tellTask $ flip (#description .~) task <$> event + +tellStatusByTime :: + TaskWidget t m r e => ((UTCTime -> Status) -> R.Event t a -> m ()) +tellStatusByTime handler ev = do + time <- getTime + tellStatus $ handler . zonedTimeToUTC <$> R.tag (R.current time) ev + +tellStatus :: TaskWidget t m r e => R.Event t Status -> m () +tellStatus ev = do + task <- getTaskInfos ^. mapping #task + tellTask $ flip (#status .~) task <$> ev + +parentButton :: forall t m r e. TaskWidget t m r e => m () +parentButton = do + task <- getTaskInfos ^. mapping #task + when (isn't (#partof % _Nothing) task) $ do + event <- button "edit" (icon "" "layers_clear") + tellTask $ (#partof .~ Nothing $ task) <$ event + +deleteButton :: forall t m r e. TaskWidget t m r e => m () +deleteButton = do + task <- getTaskInfos ^. mapping #task + deleteWidget $ task ^. #status + where + deleteWidget :: Status -> m () + deleteWidget (Status.Deleted time) = do + event <- dateSelectionWidget "deleted" $ Just time + tellStatus $ maybe Status.Pending Status.Deleted <$> event + deleteWidget _ = + button "edit" (icon "" "delete") >>= tellStatusByTime Status.Deleted + +completedWidget :: forall t m r e. TaskWidget t m r e => m () +completedWidget = do + status <- getTaskInfos ^. mapping (#task % #status) + whenJust (status ^? #_Completed) $ \time -> do + event <- dateSelectionWidget "completed" $ Just time + tellStatus $ maybe Status.Pending Status.Completed <$> event + +statusWidget :: forall t m r e. TaskWidget t m r e => m () +statusWidget = do + status <- getTaskInfos <&> (\t -> (t ^. #status, t ^. #blocked)) + widget . widgetState $ status + where + widget :: (Text, Text, Maybe (Text, Text, UTCTime -> Status.Status)) -> m () + widget (iconName, showClass, handlerMay) = do + let (altIcon, handler) = case handlerMay of + Nothing -> (D.blank, const D.blank) + Just (altIconLabel, altClass, handlerPure) -> + ( D.elClass "i" ("material-icons " <> altClass <> " showable") $ + D.text altIconLabel + , tellStatusByTime handlerPure . D.domEvent D.Mouseup + ) + (el, ()) <- D.elAttr' "div" ("class" =: "checkbox") $ do + D.elClass + "i" + ( "material-icons " <> showClass + <> if isJust handlerMay + then " hideable" + else "" + ) + $ D.text iconName + altIcon + handler el + widgetState :: + (Status, Bool) -> + (Text, Text, Maybe (Text, Text, UTCTime -> Status.Status)) + widgetState = \case + (Status.Pending, False) -> + ("done", "hide", Just ("done", "grey", Status.Completed)) + (Status.Pending, True) -> + ("block", "show", Just ("done", "grey", Status.Completed)) + (Status.Completed{}, _) -> + ("done", "show", Just ("done", "hide", const Status.Pending)) + (Status.Deleted{}, _) -> + ("delete", "show", Just ("done", "hide", const Status.Pending)) + (Status.Recurring{}, _) -> ("repeat", "show", Nothing) + +collapseButton :: forall t m r e. TaskWidget t m r e => m () +collapseButton = do + taskInfos <- getTaskInfos + hasChildren <- + R.holdUniqDyn . fmap (any (has (#task % #status % #_Pending))) + =<< lookupTasksM + (taskInfos ^. #children) + D.dyn_ $ + when <$> hasChildren ?? do + open <- getIsExpanded $ taskInfos ^. #uuid + let label = \case + True -> "unfold_less" + False -> "unfold_more" + buttonEvent <- + button "slimButton" $ + D.dyn_ (icon "collapse" . label <$> open) + tellToggle $ taskInfos ^. #uuid <$ buttonEvent diff --git a/apps/kassandra/kassandra/src/Kassandra/TextEditWidget.hs b/apps/kassandra/kassandra/src/Kassandra/TextEditWidget.hs new file mode 100644 index 00000000..74d6f557 --- /dev/null +++ b/apps/kassandra/kassandra/src/Kassandra/TextEditWidget.hs @@ -0,0 +1,57 @@ +module Kassandra.TextEditWidget ( + lineWidget, + createTextWidget, + enterTextWidget, + editText, +) where + +import Kassandra.BaseWidgets ( + button, + icon, + stateWidget, + ) +import Kassandra.Types (Widget) +import qualified Reflex as R +import qualified Reflex.Dom as D + +lineWidget :: Widget t m => Text -> m (R.Event t Text) +lineWidget text = enterTextWidget text (showText text) + +createTextWidget :: Widget t m => m (R.Event t ()) -> m (R.Event t Text) +createTextWidget = enterTextWidget "" + +enterTextWidget :: Widget t m => Text -> m (R.Event t ()) -> m (R.Event t Text) +enterTextWidget text altLabel = stateWidget False (selectWidget text altLabel) + +selectWidget :: + Widget t m => + Text -> + m (R.Event t ()) -> + Bool -> + m (R.Event t Text, R.Event t Bool) +selectWidget text _ True = do + editEvent <- editText text + pure (R.fmapMaybe id editEvent, False <$ editEvent) +selectWidget _ altLabel False = do + editEvent <- altLabel + pure (R.never, True <$ editEvent) + +-- ! Takes a dynamic text and fires an event, when the user wants to edit it. +showText :: Widget t m => Text -> m (R.Event t ()) +showText text = do + D.text text + button "edit slimButton" $ icon "" "edit" + +-- ! Prompts the user for a text edit and fires an event, when the user confirms the result. Nothing is cancelation. +editText :: Widget t m => Text -> m (R.Event t (Maybe Text)) +editText text = D.elClass "span" "activeEdit" $ do + textinput <- + D.inputElement $ D.def & lensVL D.inputElementConfig_initialValue .~ text + saveButton <- button "" $ icon "" "save" + let saveEvent = + Just + <$> R.tag + (textinput ^. to D._inputElement_value % #current) + (D.keypress D.Enter textinput <> saveButton) + cancelEvent <- button "" $ icon "" "cancel" + pure $ R.leftmost [saveEvent, Nothing <$ cancelEvent] diff --git a/apps/kassandra/kassandra/src/Kassandra/TimeWidgets.hs b/apps/kassandra/kassandra/src/Kassandra/TimeWidgets.hs new file mode 100644 index 00000000..6015fdd1 --- /dev/null +++ b/apps/kassandra/kassandra/src/Kassandra/TimeWidgets.hs @@ -0,0 +1,91 @@ +module Kassandra.TimeWidgets ( + dateSelectionWidget, +) where + +import Kassandra.BaseWidgets ( + button, + icon, + stateWidget, + ) +import Kassandra.TextEditWidget (editText) +import Kassandra.Types ( + StandardWidget, + Widget, + getTime, + ) +import qualified Reflex as R +import qualified Reflex.Dom as D + +myFormatTime :: ZonedTime -> Text +myFormatTime = toText . formatTime defaultTimeLocale "%Y-%m-%d %H:%M" + +myParseTime :: Text -> Either String LocalTime +myParseTime ((^. unpacked) -> t) = + maybeToRight ("'" <> t <> "' cannot be parsed as '%Y-%m-%d %H:%M'.") + . parseTimeM True defaultTimeLocale "%Y-%m-%d %H:%M" + $ t + +inputDateWidget :: + forall t m. Widget t m => ZonedTime -> m (R.Event t (Maybe ZonedTime)) +inputDateWidget time = do + textMayEvent <- + editText @t @m . myFormatTime $ time :: m (R.Event t (Maybe Text)) + let cancelEvent = Nothing <$ R.ffilter isNothing textMayEvent + textEvent = R.fmapMaybe id textMayEvent + (failEvent, timeEvent) = R.fanEither $ myParseTime <$> textEvent + warning <- R.holdDyn "" $ toText <$> failEvent + D.elClass "span" "warning" $ D.dynText warning + pure $ + R.leftmost + [ Just . (\t -> time{zonedTimeToLocalTime = t}) <$> timeEvent + , cancelEvent + ] + +dateSelectionWidget :: + forall t m r e. + StandardWidget t m r e => + Text -> + Maybe UTCTime -> + m (R.Event t (Maybe UTCTime)) +dateSelectionWidget label utcTime = do + timeZone <- zonedTimeZone <$> (R.sample . R.current =<< getTime) + let timeDyn = utcToZonedTime timeZone <$> utcTime + timeEvent <- + fmap (Just . zonedTimeToUTC) + <$> stateWidget False (selectTimeWidget label timeDyn) + deleteEvent <- deleteTime $ isJust utcTime + pure $ R.leftmost [deleteEvent, timeEvent] + where + deleteTime :: Bool -> m (R.Event t (Maybe a)) + deleteTime False = pure R.never + deleteTime True = do + event <- button "edit" $ icon "" "delete" + pure $ Nothing <$ event + +selectTimeWidget :: + (StandardWidget t m r e) => + Text -> + Maybe ZonedTime -> + Bool -> + m (R.Event t ZonedTime, R.Event t Bool) +selectTimeWidget label time True = do + D.elClass "span" "" $ D.text (label <> ": ") + currentTimeDyn <- getTime + currentTime <- R.sample . R.current $ currentTimeDyn + editEvent <- inputDateWidget $ fromMaybe currentTime time + pure (R.fmapMaybe id editEvent, False <$ editEvent) +selectTimeWidget label time False = do + editEvent <- showTime label time + pure (R.never, True <$ editEvent) + +showTime :: + forall t m. Widget t m => Text -> Maybe ZonedTime -> m (R.Event t ()) +showTime label = maybe create showWithButton + where + showWithButton time = do + D.el "span" $ D.text label + D.text . myFormatTime $ time + button "edit" $ icon "" "edit" + create = button "edit" $ do + icon "" "add" + D.elClass "span" "edit" $ D.text label diff --git a/apps/kassandra/kassandra/src/Kassandra/Types.hs b/apps/kassandra/kassandra/src/Kassandra/Types.hs new file mode 100644 index 00000000..f9be8d3e --- /dev/null +++ b/apps/kassandra/kassandra/src/Kassandra/Types.hs @@ -0,0 +1,154 @@ +module Kassandra.Types ( + Widget, + WidgetIO, + WidgetJSM, + TaskInfos (..), + TaskState, + AppStateChange, + TaskTreeState, + TaskTreeStateChange, + DataChange (..), + FilterState (FilterState), + StandardWidget, + TaskTreeWidget, + ExpandedTasks, + ToggleEvent (ToggleEvent), + WriteApp, + Write, + Have, + HaveApp, + AppState (AppState), + SelectState, + getAppState, + getTasks, + getSelectState, + getTime, + getIsExpanded, + getExpandedTasks, +) where + +import qualified Data.Aeson as Aeson +import Data.HashSet (member) +import Kassandra.Calendar +import Kassandra.Config (DefinitionElement, UIConfig) +import Language.Javascript.JSaddle (MonadJSM) +import qualified Reflex as R +import qualified Reflex.Dom as D +import qualified Taskwarrior.Status +import qualified Taskwarrior.Task +import Text.Show + +type Widget t m = + ( D.DomBuilder t m + , D.DomBuilderSpace m ~ D.GhcjsDomSpace + , MonadFix m + , R.MonadHold t m + , R.PostBuild t m + , MonadIO m + , R.TriggerEvent t m + , R.PerformEvent t m + , MonadIO (R.Performable m) + , HasCallStack + ) +type WidgetJSM t m = + (MonadJSM (R.Performable m), MonadJSM m, WidgetIO t m) +type WidgetIO t m = Widget t m +data TaskInfos = TaskInfos {task :: Task, children :: Seq UUID, parents :: Seq UUID, revDepends :: Seq UUID, blocked :: Bool} deriving stock (Eq, Show, Generic) +makeLabels ''TaskInfos + +type TaskState = HashMap UUID TaskInfos + +data DataChange = ChangeTask Task | CreateTask Text (Task -> Task) | SetEventList Text CalendarList deriving stock (Generic) +instance Show DataChange where + show (ChangeTask task) = [i|ChangeTask (#{task})|] + show (CreateTask name _) = [i|CreateTask with name #{name}|] + show (SetEventList uid list) = [i|SetEventList #{uid} #{list}|] +makePrismLabels ''DataChange + +data ToggleEvent = ToggleEvent UUID Bool deriving stock (Eq, Show, Generic) +makePrismLabels ''ToggleEvent + +type SelectState = Seq DefinitionElement +type AppStateChange = Either SelectState DataChange +type TaskTreeStateChange = Either AppStateChange ToggleEvent + +instance {-# OVERLAPPING #-} AsType AppStateChange AppStateChange where + _Typed = castOptic equality +instance {-# OVERLAPPING #-} AsType TaskTreeStateChange TaskTreeStateChange where + _Typed = castOptic equality + +data FilterState = FilterState {deletedFade :: NominalDiffTime, completedFade :: NominalDiffTime} deriving stock (Eq, Show, Generic) +makeLabels ''FilterState + +type ExpandedTasks = HashSet UUID +type TaskTreeState t = R.Dynamic t ExpandedTasks + +data AppState t = AppState + { taskState :: R.Dynamic t TaskState + , currentTime :: R.Dynamic t ZonedTime + , selectState :: R.Dynamic t SelectState + , calendarEvents :: R.Dynamic t (Seq CalendarEvent) + , uiConfig :: R.Dynamic t UIConfig + } + deriving stock (Generic) +makeLabels ''AppState + +type Have m r s = (MonadReader r m, HasType s r) +type HaveApp t m r = (R.Reflex t, Have m r (AppState t)) +type HaveTaskTree t m r = (Have m r (TaskTreeState t)) +type Write t m e s = (R.Reflex t, R.EventWriter t (NESeq e) m, AsType s e) +type WriteApp t m e = (Write t m e AppStateChange) +type WriteTaskTree t m e = (Write t m e TaskTreeStateChange) +type StandardWidget t m r e = (Widget t m, HaveApp t m r, WriteApp t m e) +type TaskTreeWidget t m r e = (StandardWidget t m r e, HaveTaskTree t m r, WriteTaskTree t m e) + +getIsExpanded :: + (Widget t m, HaveTaskTree t m r) => UUID -> m (R.Dynamic t Bool) +getIsExpanded uuid = R.holdUniqDyn . fmap (member uuid) =<< getExpandedTasks + +getExpandedTasks :: HaveTaskTree t m r => m (TaskTreeState t) +getExpandedTasks = asks (^. typed) + +getAppState :: (MonadReader r m, HasType (AppState t) r) => m (AppState t) +getAppState = asks (^. typed) + +getTasks :: (MonadReader r m, HasType (AppState t) r) => m (R.Dynamic t TaskState) +getTasks = getAppState ^. mapping #taskState + +getSelectState :: (MonadReader r m, HasType (AppState t) r) => m (R.Dynamic t SelectState) +getSelectState = getAppState ^. mapping #selectState +getTime :: + (MonadReader r m, HasType (AppState t) r) => m (R.Dynamic t ZonedTime) +getTime = getAppState ^. mapping #currentTime + +deriving stock instance Generic Task +makeLabels ''Task +deriving stock instance Generic Status +makeLabels ''Status +makePrismLabels ''Status +deriving stock instance Generic (Aeson.Result a) +makePrismLabels ''Aeson.Result + +instance LabelOptic "partof" A_Lens Task Task (Maybe UUID) (Maybe UUID) where + labelOptic = + lens + ((^. #uda % at "partof") >=> (^? #_Success) . fromJSON) + (\task uuid -> #uda % at "partof" .~ (toJSON <$> uuid) $ task) +instance LabelOptic "description" A_Lens TaskInfos TaskInfos Text Text where + labelOptic = #task % #description +instance LabelOptic "uuid" A_Lens TaskInfos TaskInfos UUID UUID where + labelOptic = #task % #uuid +instance LabelOptic "status" A_Lens TaskInfos TaskInfos Status Status where + labelOptic = #task % #status +instance LabelOptic "tags" A_Lens TaskInfos TaskInfos (Set Text) (Set Text) where + labelOptic = #task % #tags +instance LabelOptic "partof" A_Lens TaskInfos TaskInfos (Maybe UUID) (Maybe UUID) where + labelOptic = #task % #partof +instance LabelOptic "modified" A_Lens TaskInfos TaskInfos (Maybe UTCTime) (Maybe UTCTime) where + labelOptic = #task % #modified +instance LabelOptic "wait" A_Lens TaskInfos TaskInfos (Maybe UTCTime) (Maybe UTCTime) where + labelOptic = #task % #wait +instance LabelOptic "depends" A_Lens TaskInfos TaskInfos (Set UUID) (Set UUID) where + labelOptic = #task % #depends +instance (R.Reflex t, c ~ R.Behavior t a) => LabelOptic "current" A_Getter (R.Dynamic t a) (R.Dynamic t a) c c where + labelOptic = to R.current diff --git a/apps/kassandra/kassandra/src/Kassandra/Util.hs b/apps/kassandra/kassandra/src/Kassandra/Util.hs new file mode 100644 index 00000000..d71b217d --- /dev/null +++ b/apps/kassandra/kassandra/src/Kassandra/Util.hs @@ -0,0 +1,91 @@ +module Kassandra.Util ( + tellSingleton, + tellTask, + tellToggle, + tellNewTask, + lookupTask, + lookupTaskM, + lookupTasks, + lookupTasksM, + lookupTasksDynM, + defDyn, + defDynDyn, + stillTodo, +) where + +import Data.HashSet (member) +import Data.Witherable +import Kassandra.Types ( + AppStateChange, + DataChange, + HaveApp, + StandardWidget, + TaskInfos, + TaskState, + TaskTreeStateChange, + TaskTreeWidget, + ToggleEvent, + Widget, + WriteApp, + getExpandedTasks, + getTasks, + ) +import qualified Reflex as R +import qualified Reflex.Dom as D + +stillTodo :: TaskInfos -> Bool +stillTodo = has (#status % #_Pending) + +tellToggle :: TaskTreeWidget t m r e => R.Event t UUID -> m () +tellToggle ev = do + expandedTasks <- getExpandedTasks <&> view #current + tellSingleton $ + (_Typed @TaskTreeStateChange % _Typed @ToggleEvent #) + <$> R.attachWith + (\tasks uuid -> #_ToggleEvent # (uuid, not $ member uuid tasks)) + expandedTasks + ev + +tellTask :: WriteApp t m e => R.Event t Task -> m () +tellTask = + tellSingleton + . fmap (_Typed @AppStateChange % _Typed @DataChange % #_ChangeTask #) + +tellNewTask :: WriteApp t m e => R.Event t (Text, Task -> Task) -> m () +tellNewTask = + tellSingleton + . fmap (_Typed @AppStateChange % _Typed @DataChange % #_CreateTask #) + +tellSingleton :: + (R.Reflex t, R.EventWriter t (NESeq event) m) => R.Event t event -> m () +tellSingleton = R.tellEvent . fmap one + +lookupTask :: TaskState -> UUID -> Maybe TaskInfos +lookupTask tasks uuid = tasks ^. at uuid + +lookupTasks :: Filterable f => TaskState -> f UUID -> f TaskInfos +lookupTasks tasks = mapMaybe (lookupTask tasks) + +lookupTaskM :: + StandardWidget t m r e => + R.Dynamic t UUID -> + m (R.Dynamic t (Maybe TaskInfos)) +lookupTaskM uuid = getTasks <&> \tasks -> R.zipDynWith lookupTask tasks uuid + +lookupTasksM :: (Filterable f, HaveApp t m r) => f UUID -> m (R.Dynamic t (f TaskInfos)) +lookupTasksM = R.constDyn >>> lookupTasksDynM + +lookupTasksDynM :: + (Filterable f, HaveApp t m r) => R.Dynamic t (f UUID) -> m (R.Dynamic t (f TaskInfos)) +lookupTasksDynM uuids = + getTasks <&> \tasks -> flip lookupTasks <$> uuids <*> tasks + +defDyn :: Widget t m => a -> R.Dynamic t (m a) -> m (R.Dynamic t a) +defDyn defVal = R.holdDyn defVal <=< D.dyn + +defDynDyn :: + Widget t m => + R.Dynamic t a -> + R.Dynamic t (m (R.Dynamic t a)) -> + m (R.Dynamic t a) +defDynDyn defDynamic = fmap join . defDyn defDynamic diff --git a/apps/kassandra/kassandra/src/Prelude.hs b/apps/kassandra/kassandra/src/Prelude.hs new file mode 100644 index 00000000..1891f9bb --- /dev/null +++ b/apps/kassandra/kassandra/src/Prelude.hs @@ -0,0 +1,132 @@ +{-# OPTIONS_GHC -Wno-deprecations #-} + +module Prelude ( + module Relude, + module Optics, + module Optics.TH, + module Data.Text.Optics, + partitionEithersNE, + Task, + i, + (<$?>), + (<&?>), + Aeson.ToJSON, + Aeson.FromJSON, + getZonedTime, + addUTCTime, + utcToZonedTime, + zonedTimeZone, + zonedTimeToUTC, + firstJust, + IOException, + UTCTime, + catch, + concurrently_, + forConcurrently_, + race_, + makeLabels, + Status, + MonadFix, + ZonedTime, + NominalDiffTime, + toJSON, + fromJSON, + UUID, + HasField', + field', + HasAny, + the, + HasType, + typed, + AsType, + _Typed, + AsConstructor', + _Ctor', + formatTime, + zonedTimeToLocalTime, + LocalTime, + defaultTimeLocale, + parseTimeM, + NESeq, + nonEmptySeq, + pattern IsEmpty, + pattern IsNonEmpty, + pattern (:<||), + pattern (:||>), + (|>),(<|), + toSeq, + mapMaybe, + filter, + partitionEithersNESeq +) where + +import Control.Concurrent.Async ( + concurrently_, + forConcurrently_, + race_, + ) +import Control.Exception ( + IOException, + catch, + ) +import Control.Monad.Fix (MonadFix) +import Data.Aeson ( + fromJSON, + toJSON, + ) +import qualified Data.Aeson as Aeson +import Data.Generics.Product.Any (HasAny (the)) +import Data.Generics.Product.Fields (HasField' (field')) +import Data.Generics.Product.Typed (HasType (typed)) +import Data.Generics.Sum.Constructors (AsConstructor' (_Ctor')) +import Data.Generics.Sum.Typed (AsType (_Typed)) +import Data.List.Extra (firstJust) +import Data.Sequence.NonEmpty hiding (filter, (|>), (<|)) +import Data.Sequence ((|>),(<|)) +import Data.String.Interpolate (i) +import Data.Text.Optics hiding (text) +import Data.These (partitionEithersNE, These(..)) +import Data.Witherable ( mapMaybe, (<$?>), (<&?>), filter ) +import Data.Time ( + UTCTime, + addUTCTime, + defaultTimeLocale, + formatTime, + getZonedTime, + parseTimeM, + utcToZonedTime, + zonedTimeToUTC, + zonedTimeZone, + ) +import Data.Time.Clock (NominalDiffTime) +import Data.Time.LocalTime ( + LocalTime, + ZonedTime, + zonedTimeToLocalTime, + ) +import Data.UUID (UUID) +import Language.Haskell.TH.Syntax ( + Dec, + Name, + Q, + ) +import Optics hiding ((|>), (<|)) +import Optics.TH +import Relude hiding (uncons, mapMaybe, filter) +import Relude.Extra.Foldable1 +import Taskwarrior.Status (Status) +import Taskwarrior.Task (Task) + +instance One (NESeq a) where + type OneItem (NESeq a) = a + one = singleton + +instance Foldable1 NESeq where + foldMap1 f = foldMapWithIndex (const f) + +-- (lensField .~ noPrefixNamer $ fieldLabelsRules) == noPrefixFieldLabels but only in optics-th 0.2 +makeLabels :: Name -> Q [Dec] +makeLabels = makeFieldLabelsWith noPrefixFieldLabels + +partitionEithersNESeq :: NESeq (Either a b) -> These (NESeq a) (NESeq b) +partitionEithersNESeq = fold1 . fmap (either (This . one) (That . one)) diff --git a/apps/kassandra/nix/sources.json b/apps/kassandra/nix/sources.json new file mode 100644 index 00000000..eadbed21 --- /dev/null +++ b/apps/kassandra/nix/sources.json @@ -0,0 +1,14 @@ +{ + "nixpkgs": { + "branch": "nixos-unstable", + "description": "Nix Packages collection", + "homepage": "", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "f1c167688a6f81f4a51ab542e5f476c8c595e457", + "sha256": "00ac3axj7jdfcajj3macdydf9w9bvqqvgrqkh1xxr3rfi9q2fz1v", + "type": "tarball", + "url": "https://github.com/NixOS/nixpkgs/archive/f1c167688a6f81f4a51ab542e5f476c8c595e457.tar.gz", + "url_template": "https://github.com///archive/.tar.gz" + } +} diff --git a/apps/kassandra/nix/sources.nix b/apps/kassandra/nix/sources.nix new file mode 100644 index 00000000..9a01c8ac --- /dev/null +++ b/apps/kassandra/nix/sources.nix @@ -0,0 +1,194 @@ +# This file has been generated by Niv. + +let + + # + # The fetchers. fetch_ fetches specs of type . + # + + fetch_file = pkgs: name: spec: + let + name' = sanitizeName name + "-src"; + in + if spec.builtin or true then + builtins_fetchurl { inherit (spec) url sha256; name = name'; } + else + pkgs.fetchurl { inherit (spec) url sha256; name = name'; }; + + fetch_tarball = pkgs: name: spec: + let + name' = sanitizeName name + "-src"; + in + if spec.builtin or true then + builtins_fetchTarball { name = name'; inherit (spec) url sha256; } + else + pkgs.fetchzip { name = name'; inherit (spec) url sha256; }; + + fetch_git = name: spec: + let + ref = + if spec ? ref then spec.ref else + if spec ? branch then "refs/heads/${spec.branch}" else + if spec ? tag then "refs/tags/${spec.tag}" else + abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!"; + submodules = if spec ? submodules then spec.submodules else false; + submoduleArg = + let + nixSupportsSubmodules = builtins.compareVersions builtins.nixVersion "2.4" >= 0; + emptyArgWithWarning = + if submodules == true + then + builtins.trace + ( + "The niv input \"${name}\" uses submodules " + + "but your nix's (${builtins.nixVersion}) builtins.fetchGit " + + "does not support them" + ) + {} + else {}; + in + if nixSupportsSubmodules + then { inherit submodules; } + else emptyArgWithWarning; + in + builtins.fetchGit + ({ url = spec.repo; inherit (spec) rev; inherit ref; } // submoduleArg); + + fetch_local = spec: spec.path; + + fetch_builtin-tarball = name: throw + ''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`. + $ niv modify ${name} -a type=tarball -a builtin=true''; + + fetch_builtin-url = name: throw + ''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`. + $ niv modify ${name} -a type=file -a builtin=true''; + + # + # Various helpers + # + + # https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695 + sanitizeName = name: + ( + concatMapStrings (s: if builtins.isList s then "-" else s) + ( + builtins.split "[^[:alnum:]+._?=-]+" + ((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name) + ) + ); + + # The set of packages used when specs are fetched using non-builtins. + mkPkgs = sources: system: + let + sourcesNixpkgs = + import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; }; + hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath; + hasThisAsNixpkgsPath = == ./.; + in + if builtins.hasAttr "nixpkgs" sources + then sourcesNixpkgs + else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then + import {} + else + abort + '' + Please specify either (through -I or NIX_PATH=nixpkgs=...) or + add a package called "nixpkgs" to your sources.json. + ''; + + # The actual fetching function. + fetch = pkgs: name: spec: + + if ! builtins.hasAttr "type" spec then + abort "ERROR: niv spec ${name} does not have a 'type' attribute" + else if spec.type == "file" then fetch_file pkgs name spec + else if spec.type == "tarball" then fetch_tarball pkgs name spec + else if spec.type == "git" then fetch_git name spec + else if spec.type == "local" then fetch_local spec + else if spec.type == "builtin-tarball" then fetch_builtin-tarball name + else if spec.type == "builtin-url" then fetch_builtin-url name + else + abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}"; + + # If the environment variable NIV_OVERRIDE_${name} is set, then use + # the path directly as opposed to the fetched source. + replace = name: drv: + let + saneName = stringAsChars (c: if isNull (builtins.match "[a-zA-Z0-9]" c) then "_" else c) name; + ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}"; + in + if ersatz == "" then drv else + # this turns the string into an actual Nix path (for both absolute and + # relative paths) + if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}"; + + # Ports of functions for older nix versions + + # a Nix version of mapAttrs if the built-in doesn't exist + mapAttrs = builtins.mapAttrs or ( + f: set: with builtins; + listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set)) + ); + + # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295 + range = first: last: if first > last then [] else builtins.genList (n: first + n) (last - first + 1); + + # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257 + stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1)); + + # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269 + stringAsChars = f: s: concatStrings (map f (stringToCharacters s)); + concatMapStrings = f: list: concatStrings (map f list); + concatStrings = builtins.concatStringsSep ""; + + # https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331 + optionalAttrs = cond: as: if cond then as else {}; + + # fetchTarball version that is compatible between all the versions of Nix + builtins_fetchTarball = { url, name ? null, sha256 }@attrs: + let + inherit (builtins) lessThan nixVersion fetchTarball; + in + if lessThan nixVersion "1.12" then + fetchTarball ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) + else + fetchTarball attrs; + + # fetchurl version that is compatible between all the versions of Nix + builtins_fetchurl = { url, name ? null, sha256 }@attrs: + let + inherit (builtins) lessThan nixVersion fetchurl; + in + if lessThan nixVersion "1.12" then + fetchurl ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) + else + fetchurl attrs; + + # Create the final "sources" from the config + mkSources = config: + mapAttrs ( + name: spec: + if builtins.hasAttr "outPath" spec + then abort + "The values in sources.json should not have an 'outPath' attribute" + else + spec // { outPath = replace name (fetch config.pkgs name spec); } + ) config.sources; + + # The "config" used by the fetchers + mkConfig = + { sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null + , sources ? if isNull sourcesFile then {} else builtins.fromJSON (builtins.readFile sourcesFile) + , system ? builtins.currentSystem + , pkgs ? mkPkgs sources system + }: rec { + # The sources, i.e. the attribute set of spec name to spec + inherit sources; + + # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers + inherit pkgs; + }; + +in +mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); } diff --git a/apps/kassandra/obelisk-shell.nix b/apps/kassandra/obelisk-shell.nix new file mode 100644 index 00000000..9e5bf488 --- /dev/null +++ b/apps/kassandra/obelisk-shell.nix @@ -0,0 +1 @@ +(import ./. { }).shells.ghc diff --git a/apps/kassandra/release.nix b/apps/kassandra/release.nix new file mode 100644 index 00000000..0249ab1d --- /dev/null +++ b/apps/kassandra/release.nix @@ -0,0 +1,19 @@ +{ pkgs ? import (import nix/sources.nix).nixpkgs { } }: +let + haskellPackages = pkgs.haskellPackages.extend ( + self: super: { + kassandra = self.callCabal2nix "kassandra" ./kassandra { }; + standalone = self.callCabal2nix "standalone" ./standalone { }; + } + ); + reflex-platform = import ./. { }; +in +{ + lib = haskellPackages.kassandra; + app = haskellPackages.standalone; + server = reflex-platform.exe; + android = pkgs.runCommand "kassandra-android-apk" { } '' + mkdir -p $out + cp ${reflex-platform.android.frontend}/android-app-release-unsigned.apk $out/de.maralorn.kassandra_${import ./code.nix}.apk + ''; +} diff --git a/apps/kassandra/shell.nix b/apps/kassandra/shell.nix new file mode 100644 index 00000000..16558f09 --- /dev/null +++ b/apps/kassandra/shell.nix @@ -0,0 +1,5 @@ +{ pkgs ? import (import nix/sources.nix).nixpkgs { } }: let + outputs = import ./release.nix { inherit pkgs; }; +in pkgs.haskellPackages.shellFor { + packages = (_: [outputs.lib outputs.app]); +} diff --git a/apps/kassandra/standalone/.hlint.yaml b/apps/kassandra/standalone/.hlint.yaml new file mode 100644 index 00000000..270feee7 --- /dev/null +++ b/apps/kassandra/standalone/.hlint.yaml @@ -0,0 +1,3086 @@ +- arguments: + - -XConstraintKinds + - -XDeriveGeneric + - -XGeneralizedNewtypeDeriving + - -XLambdaCase + - -XOverloadedStrings + - -XRecordWildCards + - -XScopedTypeVariables + - -XStandaloneDeriving + - -XTupleSections + - -XTypeApplications + - -XViewPatterns +- ignore: + name: Use head +- ignore: + name: Use Foldable.forM_ +- hint: + lhs: pure () + note: Use 'pass' + rhs: pass +- hint: + lhs: return () + note: Use 'pass' + rhs: pass +- hint: + lhs: '(: [])' + note: Use `one` + rhs: one +- hint: + lhs: (:| []) + note: Use `one` + rhs: one +- hint: + lhs: Data.Sequence.singleton + note: Use `one` + rhs: one +- hint: + lhs: Data.Text.singleton + note: Use `one` + rhs: one +- hint: + lhs: Data.Text.Lazy.singleton + note: Use `one` + rhs: one +- hint: + lhs: Data.ByteString.singleton + note: Use `one` + rhs: one +- hint: + lhs: Data.ByteString.Lazy.singleton + note: Use `one` + rhs: one +- hint: + lhs: Data.Map.singleton + note: Use `one` + rhs: one +- hint: + lhs: Data.Map.Strict.singleton + note: Use `one` + rhs: one +- hint: + lhs: Data.HashMap.Strict.singleton + note: Use `one` + rhs: one +- hint: + lhs: Data.HashMap.Lazy.singleton + note: Use `one` + rhs: one +- hint: + lhs: Data.IntMap.singleton + note: Use `one` + rhs: one +- hint: + lhs: Data.IntMap.Strict.singleton + note: Use `one` + rhs: one +- hint: + lhs: Data.Set.singleton + note: Use `one` + rhs: one +- hint: + lhs: Data.HashSet.singleton + note: Use `one` + rhs: one +- hint: + lhs: Data.IntSet.singleton + note: Use `one` + rhs: one +- 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 +- hint: + lhs: Control.Exception.throw + note: Use 'impureThrow' + rhs: impureThrow +- warn: + lhs: Data.Text.IO.readFile + rhs: readFileText +- warn: + lhs: Data.Text.IO.writeFile + rhs: writeFileText +- warn: + lhs: Data.Text.IO.appendFile + rhs: appendFileText +- warn: + lhs: Data.Text.Lazy.IO.readFile + rhs: readFileLText +- warn: + lhs: Data.Text.Lazy.IO.writeFile + rhs: writeFileLText +- warn: + lhs: Data.Text.Lazy.IO.appendFile + rhs: appendFileLText +- warn: + lhs: Data.ByteString.readFile + rhs: readFileBS +- warn: + lhs: Data.ByteString.writeFile + rhs: writeFileBS +- warn: + lhs: Data.ByteString.appendFile + rhs: appendFileBS +- warn: + lhs: Data.ByteString.Lazy.readFile + rhs: readFileLBS +- warn: + lhs: Data.ByteString.Lazy.writeFile + rhs: writeFileLBS +- warn: + lhs: Data.ByteString.Lazy.appendFile + rhs: appendFileLBS +- hint: + lhs: foldl' (flip f) + note: Use 'flipfoldl'' + rhs: flipfoldl' f +- warn: + lhs: foldl' (+) 0 + rhs: sum +- warn: + lhs: foldl' (*) 1 + rhs: product +- hint: + lhs: fmap and (sequence s) + note: Applying this hint would mean that some actions that were being executed + previously would no longer be executed. + rhs: andM s +- hint: + lhs: and <$> sequence s + note: Applying this hint would mean that some actions that were being executed + previously would no longer be executed. + rhs: andM s +- hint: + lhs: fmap or (sequence s) + note: Applying this hint would mean that some actions that were being executed + previously would no longer be executed. + rhs: orM s +- hint: + lhs: or <$> sequence s + note: Applying this hint would mean that some actions that were being executed + previously would no longer be executed. + rhs: orM s +- hint: + lhs: fmap and (mapM f s) + note: Applying this hint would mean that some actions that were being executed + previously would no longer be executed. + rhs: allM f s +- hint: + lhs: and <$> mapM f s + note: Applying this hint would mean that some actions that were being executed + previously would no longer be executed. + rhs: allM f s +- hint: + lhs: fmap or (mapM f s) + note: Applying this hint would mean that some actions that were being executed + previously would no longer be executed. + rhs: anyM f s +- hint: + lhs: or <$> mapM f s + note: Applying this hint would mean that some actions that were being executed + previously would no longer be executed. + rhs: anyM f s +- warn: + lhs: getAlt (foldMap (Alt . f) xs) + rhs: asumMap xs +- warn: + lhs: getAlt . foldMap (Alt . f) + rhs: asumMap +- hint: + lhs: foldr (\x acc -> f x <|> acc) empty + note: Use 'asumMap' + rhs: asumMap f +- hint: + lhs: asum (map f xs) + note: Use 'asumMap' + rhs: asumMap f xs +- warn: + lhs: map fst &&& map snd + rhs: unzip +- hint: + lhs: fmap (fmap f) x + note: Use '(<<$>>)' + rhs: f <<$>> x +- hint: + lhs: (\f -> f x) <$> ff + note: Use flap operator + rhs: ff ?? x +- hint: + lhs: fmap (\f -> f x) ff + note: Use flap operator + rhs: ff ?? x +- hint: + lhs: fmap ($ x) ff + note: Use flap operator + rhs: ff ?? x +- hint: + lhs: ($ x) <$> ff + note: Use flap operator + rhs: ff ?? x +- warn: + lhs: fmap f (nonEmpty x) + rhs: viaNonEmpty f x +- warn: + lhs: fmap f . nonEmpty + rhs: viaNonEmpty f +- warn: + lhs: f <$> nonEmpty x + rhs: viaNonEmpty f x +- warn: + lhs: f >>= guard + rhs: guardM f +- warn: + lhs: guard =<< f + rhs: guardM f +- 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 l +- warn: + lhs: maybe (Right r) Left + rhs: maybeToLeft r +- warn: + lhs: case m of Just x -> f x; Nothing -> pure () + rhs: whenJust m f +- warn: + lhs: case m of Just x -> f x; Nothing -> return () + rhs: whenJust m f +- warn: + lhs: case m of Just x -> f x; Nothing -> pass + rhs: whenJust m f +- warn: + lhs: case m of Nothing -> pure () ; Just x -> f x + rhs: whenJust m f +- warn: + lhs: case m of Nothing -> return (); Just x -> f x + rhs: whenJust m f +- warn: + lhs: case m of Nothing -> pass ; Just x -> f x + rhs: whenJust m f +- warn: + lhs: maybe (pure ()) f m + rhs: whenJust m f +- warn: + lhs: maybe (return ()) f m + rhs: whenJust m f +- warn: + lhs: maybe pass f m + rhs: whenJust m f +- warn: + lhs: m >>= \a -> whenJust a f + rhs: whenJustM m f +- warn: + lhs: m >>= \case Just x -> f x; Nothing -> pure () + rhs: whenJustM m f +- warn: + lhs: m >>= \case Just x -> f x; Nothing -> return () + rhs: whenJustM m f +- warn: + lhs: m >>= \case Just x -> f x; Nothing -> pass + rhs: whenJustM m f +- warn: + lhs: m >>= \case Nothing -> pure () ; Just x -> f x + rhs: whenJustM m f +- warn: + lhs: m >>= \case Nothing -> return (); Just x -> f x + rhs: whenJustM m f +- warn: + lhs: m >>= \case Nothing -> pass ; Just x -> f x + rhs: whenJustM m f +- warn: + lhs: maybe (pure ()) f =<< m + rhs: whenJustM m f +- warn: + lhs: maybe (return ()) f =<< m + rhs: whenJustM m f +- warn: + lhs: maybe pass f =<< m + rhs: whenJustM m f +- warn: + lhs: m >>= maybe (pure ()) f + rhs: whenJustM m f +- warn: + lhs: m >>= maybe (return ()) f + rhs: whenJustM m f +- warn: + lhs: m >>= maybe pass f + rhs: whenJustM m f +- warn: + lhs: case m of Just _ -> pure () ; Nothing -> x + rhs: whenNothing_ m x +- warn: + lhs: case m of Just _ -> return (); Nothing -> x + rhs: whenNothing_ m x +- warn: + lhs: case m of Just _ -> pass ; Nothing -> x + rhs: whenNothing_ m x +- warn: + lhs: case m of Nothing -> x; Just _ -> pure () + rhs: whenNothing_ m x +- warn: + lhs: case m of Nothing -> x; Just _ -> return () + rhs: whenNothing_ m x +- warn: + lhs: case m of Nothing -> x; Just _ -> pass + rhs: whenNothing_ m x +- warn: + lhs: maybe x (\_ -> pure () ) m + rhs: whenNothing_ m x +- warn: + lhs: maybe x (\_ -> return () ) m + rhs: whenNothing_ m x +- warn: + lhs: maybe x (\_ -> pass ) m + rhs: whenNothing_ m x +- warn: + lhs: maybe x (const (pure () )) m + rhs: whenNothing_ m x +- warn: + lhs: maybe x (const (return ())) m + rhs: whenNothing_ m x +- warn: + lhs: maybe x (const pass) m + rhs: whenNothing_ m x +- warn: + lhs: m >>= \a -> whenNothing_ a x + rhs: whenNothingM_ m x +- warn: + lhs: m >>= \case Just _ -> pure () ; Nothing -> x + rhs: whenNothingM_ m x +- warn: + lhs: m >>= \case Just _ -> return (); Nothing -> x + rhs: whenNothingM_ m x +- warn: + lhs: m >>= \case Just _ -> pass ; Nothing -> x + rhs: whenNothingM_ m x +- warn: + lhs: m >>= \case Nothing -> x; Just _ -> pure () + rhs: whenNothingM_ m x +- warn: + lhs: m >>= \case Nothing -> x; Just _ -> return () + rhs: whenNothingM_ m x +- warn: + lhs: m >>= \case Nothing -> x; Just _ -> pass + rhs: whenNothingM_ m x +- warn: + lhs: maybe x (\_ -> pure () ) =<< m + rhs: whenNothingM_ m x +- warn: + lhs: maybe x (\_ -> return () ) =<< m + rhs: whenNothingM_ m x +- warn: + lhs: maybe x (\_ -> pass ) =<< m + rhs: whenNothingM_ m x +- warn: + lhs: maybe x (const (pure () )) =<< m + rhs: whenNothingM_ m x +- warn: + lhs: maybe x (const (return ())) =<< m + rhs: whenNothingM_ m x +- warn: + lhs: maybe x (const pass) =<< m + rhs: whenNothingM_ m x +- warn: + lhs: m >>= maybe x (\_ -> pure ()) + rhs: whenNothingM_ m x +- warn: + lhs: m >>= maybe x (\_ -> return ()) + rhs: whenNothingM_ m x +- warn: + lhs: m >>= maybe x (\_ -> pass) + rhs: whenNothingM_ m x +- warn: + lhs: m >>= maybe x (const (pure ()) ) + rhs: whenNothingM_ m x +- warn: + lhs: m >>= maybe x (const (return ())) + rhs: whenNothingM_ m x +- warn: + lhs: m >>= maybe x (const pass) + rhs: whenNothingM_ m x +- warn: + lhs: whenLeft () + rhs: whenLeft_ +- warn: + lhs: case m of Left x -> f x; Right _ -> pure () + rhs: whenLeft_ m f +- warn: + lhs: case m of Left x -> f x; Right _ -> return () + rhs: whenLeft_ m f +- warn: + lhs: case m of Left x -> f x; Right _ -> pass + rhs: whenLeft_ m f +- warn: + lhs: case m of Right _ -> pure () ; Left x -> f x + rhs: whenLeft_ m f +- warn: + lhs: case m of Right _ -> return (); Left x -> f x + rhs: whenLeft_ m f +- warn: + lhs: case m of Right _ -> pass ; Left x -> f x + rhs: whenLeft_ m f +- warn: + lhs: either f (\_ -> pure () ) m + rhs: whenLeft_ m f +- warn: + lhs: either f (\_ -> return () ) m + rhs: whenLeft_ m f +- warn: + lhs: either f (\_ -> pass ) m + rhs: whenLeft_ m f +- warn: + lhs: either f (const (pure () )) m + rhs: whenLeft_ m f +- warn: + lhs: either f (const (return ())) m + rhs: whenLeft_ m f +- warn: + lhs: either f (const pass) m + rhs: whenLeft_ m f +- warn: + lhs: m >>= \a -> whenLeft_ a f + rhs: whenLeftM_ m f +- warn: + lhs: m >>= \case Left x -> f x; Right _ -> pure () + rhs: whenLeftM_ m f +- warn: + lhs: m >>= \case Left x -> f x; Right _ -> return () + rhs: whenLeftM_ m f +- warn: + lhs: m >>= \case Left x -> f x; Right _ -> pass + rhs: whenLeftM_ m f +- warn: + lhs: m >>= \case Right _ -> pure () ; Left x -> f x + rhs: whenLeftM_ m f +- warn: + lhs: m >>= \case Right _ -> return (); Left x -> f x + rhs: whenLeftM_ m f +- warn: + lhs: m >>= \case Right _ -> pass ; Left x -> f x + rhs: whenLeftM_ m f +- warn: + lhs: either f (\_ -> pure () ) =<< m + rhs: whenLeftM_ m f +- warn: + lhs: either f (\_ -> return () ) =<< m + rhs: whenLeftM_ m f +- warn: + lhs: either f (\_ -> pass ) =<< m + rhs: whenLeftM_ m f +- warn: + lhs: either f (const (pure () )) =<< m + rhs: whenLeftM_ m f +- warn: + lhs: either f (const (return ())) =<< m + rhs: whenLeftM_ m f +- warn: + lhs: either f (const pass) =<< m + rhs: whenLeftM_ m f +- warn: + lhs: m >>= either f (\_ -> pure ()) + rhs: whenLeftM_ m f +- warn: + lhs: m >>= either f (\_ -> return ()) + rhs: whenLeftM_ m f +- warn: + lhs: m >>= either f (\_ -> pass) + rhs: whenLeftM_ m f +- warn: + lhs: m >>= either f (const (pure ()) ) + rhs: whenLeftM_ m f +- warn: + lhs: m >>= either f (const (return ())) + rhs: whenLeftM_ m f +- warn: + lhs: m >>= either f (const pass) + rhs: whenLeftM_ m f +- warn: + lhs: whenRight () + rhs: whenRight_ +- warn: + lhs: case m of Right x -> f x; Left _ -> pure () + rhs: whenRight_ m f +- warn: + lhs: case m of Right x -> f x; Left _ -> return () + rhs: whenRight_ m f +- warn: + lhs: case m of Right x -> f x; Left _ -> pass + rhs: whenRight_ m f +- warn: + lhs: case m of Left _ -> pure () ; Right x -> f x + rhs: whenRight_ m f +- warn: + lhs: case m of Left _ -> return (); Right x -> f x + rhs: whenRight_ m f +- warn: + lhs: case m of Left _ -> pass ; Right x -> f x + rhs: whenRight_ m f +- warn: + lhs: either (\_ -> pure () ) f m + rhs: whenRight_ m f +- warn: + lhs: either (\_ -> return () ) f m + rhs: whenRight_ m f +- warn: + lhs: either (\_ -> pass ) f m + rhs: whenRight_ m f +- warn: + lhs: either (const (pure () )) f m + rhs: whenRight_ m f +- warn: + lhs: either (const (return ())) f m + rhs: whenRight_ m f +- warn: + lhs: either (const pass) f m + rhs: whenRight_ m f +- warn: + lhs: m >>= \a -> whenRight_ a f + rhs: whenRightM_ m f +- warn: + lhs: 'm >>= \case Right x -> f x; Left _ -> pure () ' + rhs: whenRightM_ m f +- warn: + lhs: m >>= \case Right x -> f x; Left _ -> return () + rhs: whenRightM_ m f +- warn: + lhs: m >>= \case Right x -> f x; Left _ -> pass + rhs: whenRightM_ m f +- warn: + lhs: m >>= \case Left _ -> pure () ; Right x -> f x + rhs: whenRightM_ m f +- warn: + lhs: m >>= \case Left _ -> return (); Right x -> f x + rhs: whenRightM_ m f +- warn: + lhs: m >>= \case Left _ -> pass ; Right x -> f x + rhs: whenRightM_ m f +- warn: + lhs: either (\_ -> pure () ) f =<< m + rhs: whenRightM_ m f +- warn: + lhs: either (\_ -> return () ) f =<< m + rhs: whenRightM_ m f +- warn: + lhs: either (\_ -> pass ) f =<< m + rhs: whenRightM_ m f +- warn: + lhs: either (const (pure () )) f =<< m + rhs: whenRightM_ m f +- warn: + lhs: either (const (return ())) f =<< m + rhs: whenRightM_ m f +- warn: + lhs: either (const pass) f =<< m + rhs: whenRightM_ m f +- warn: + lhs: m >>= either (\_ -> pure ()) f + rhs: whenRightM_ m f +- warn: + lhs: m >>= either (\_ -> return ()) f + rhs: whenRightM_ m f +- warn: + lhs: m >>= either (\_ -> pass) f + rhs: whenRightM_ m f +- warn: + lhs: m >>= either (const (pure ()) ) f + rhs: whenRightM_ m f +- warn: + lhs: m >>= either (const (return ())) f + rhs: whenRightM_ m f +- warn: + lhs: m >>= either (const pass) f + rhs: whenRightM_ m f +- warn: + lhs: 'case m of Left x -> f x; Right _ -> pure d ' + rhs: whenLeft d m f +- warn: + lhs: case m of Left x -> f x; Right _ -> return d + rhs: whenLeft d m f +- warn: + lhs: case m of Right _ -> pure d ; Left x -> f x + rhs: whenLeft d m f +- warn: + lhs: case m of Right _ -> return d; Left x -> f x + rhs: whenLeft d m f +- warn: + lhs: either f (\_ -> pure d ) m + rhs: whenLeft d m f +- warn: + lhs: either f (\_ -> return d ) m + rhs: whenLeft d m f +- warn: + lhs: either f (const (pure d )) m + rhs: whenLeft d m f +- warn: + lhs: either f (const (return d)) m + rhs: whenLeft d m f +- warn: + lhs: m >>= \a -> whenLeft d a f + rhs: whenLeftM d m f +- warn: + lhs: m >>= \case Left x -> f x; Right _ -> pure d + rhs: whenLeftM d m f +- warn: + lhs: m >>= \case Left x -> f x; Right _ -> return d + rhs: whenLeftM d m f +- warn: + lhs: m >>= \case Right _ -> pure d ; Left x -> f x + rhs: whenLeftM d m f +- warn: + lhs: m >>= \case Right _ -> return d; Left x -> f x + rhs: whenLeftM d m f +- warn: + lhs: either f (\_ -> pure d ) =<< m + rhs: whenLeftM d m f +- warn: + lhs: either f (\_ -> return d ) =<< m + rhs: whenLeftM d m f +- warn: + lhs: either f (const (pure d )) =<< m + rhs: whenLeftM d m f +- warn: + lhs: either f (const (return d)) =<< m + rhs: whenLeftM d m f +- warn: + lhs: m >>= either f (\_ -> pure d) + rhs: whenLeftM d m f +- warn: + lhs: m >>= either f (\_ -> return d) + rhs: whenLeftM d m f +- warn: + lhs: m >>= either f (const (pure d)) + rhs: whenLeftM d m f +- warn: + lhs: m >>= either f (const (return d)) + rhs: whenLeftM d m f +- warn: + lhs: case m of Right x -> f x; Left _ -> pure d + rhs: whenRight d m f +- warn: + lhs: case m of Right x -> f x; Left _ -> return d + rhs: whenRight d m f +- warn: + lhs: case m of Left _ -> pure d ; Right x -> f x + rhs: whenRight d m f +- warn: + lhs: case m of Left _ -> return d; Right x -> f x + rhs: whenRight d m f +- warn: + lhs: either (\_ -> pure d ) f m + rhs: whenRight d m f +- warn: + lhs: either (\_ -> return d ) f m + rhs: whenRight d m f +- warn: + lhs: either (const (pure d )) f m + rhs: whenRight d m f +- warn: + lhs: either (const (return d)) f m + rhs: whenRight d m f +- warn: + lhs: m >>= \a -> whenRight d a f + rhs: whenRightM d m f +- warn: + lhs: m >>= \case Right x -> f x; Left _ -> pure d + rhs: whenRightM d m f +- warn: + lhs: m >>= \case Right x -> f x; Left _ -> return d + rhs: whenRightM d m f +- warn: + lhs: m >>= \case Left _ -> pure d ; Right x -> f x + rhs: whenRightM d m f +- warn: + lhs: m >>= \case Left _ -> return d; Right x -> f x + rhs: whenRightM d m f +- warn: + lhs: either (\_ -> pure d ) f =<< m + rhs: whenRightM d m f +- warn: + lhs: either (\_ -> return d ) f =<< m + rhs: whenRightM d m f +- warn: + lhs: either (const (pure d )) f =<< m + rhs: whenRightM d m f +- warn: + lhs: either (const (return d)) f =<< m + rhs: whenRightM d m f +- warn: + lhs: m >>= either (\_ -> pure d) f + rhs: whenRightM d m f +- warn: + lhs: m >>= either (\_ -> return d) f + rhs: whenRightM d m f +- warn: + lhs: m >>= either (const (pure d) ) f + rhs: whenRightM d m f +- warn: + lhs: m >>= either (const (return d)) f + rhs: whenRightM d m f +- warn: + lhs: case m of [] -> return (); (x:xs) -> f (x :| xs) + rhs: whenNotNull m f +- warn: + lhs: case m of [] -> pure () ; (x:xs) -> f (x :| xs) + rhs: whenNotNull m f +- warn: + lhs: case m of [] -> pass ; (x:xs) -> f (x :| xs) + rhs: whenNotNull m f +- warn: + lhs: case m of (x:xs) -> f (x :| xs); [] -> return () + rhs: whenNotNull m f +- warn: + lhs: 'case m of (x:xs) -> f (x :| xs); [] -> pure () ' + rhs: whenNotNull m f +- warn: + lhs: 'case m of (x:xs) -> f (x :| xs); [] -> pass ' + rhs: whenNotNull m f +- warn: + lhs: m >>= \case [] -> pass ; (x:xs) -> f (x :| xs) + rhs: whenNotNullM m f +- warn: + lhs: m >>= \case [] -> pure () ; (x:xs) -> f (x :| xs) + rhs: whenNotNullM m f +- warn: + lhs: m >>= \case [] -> return (); (x:xs) -> f (x :| xs) + rhs: whenNotNullM m f +- warn: + lhs: 'm >>= \case (x:xs) -> f (x :| xs); [] -> pass ' + rhs: whenNotNullM m f +- warn: + lhs: 'm >>= \case (x:xs) -> f (x :| xs); [] -> pure () ' + rhs: whenNotNullM m f +- warn: + lhs: m >>= \case (x:xs) -> f (x :| xs); [] -> return () + rhs: whenNotNullM m f +- warn: + lhs: mapMaybe leftToMaybe + rhs: lefts +- warn: + lhs: mapMaybe rightToMaybe + rhs: rights +- warn: + lhs: flip runReaderT + rhs: usingReaderT +- warn: + lhs: flip runReader + rhs: usingReader +- warn: + lhs: flip runStateT + rhs: usingStateT +- warn: + lhs: flip runState + rhs: usingState +- warn: + lhs: fst <$> usingStateT s st + rhs: evaluatingStateT s st +- warn: + lhs: fst (usingState s st) + rhs: evaluatingState s st +- warn: + lhs: snd <$> usingStateT s st + rhs: executingStateT s st +- warn: + lhs: snd (usingState s st) + rhs: executingState s st +- warn: + lhs: MaybeT (pure m) + rhs: hoistMaybe m +- warn: + lhs: MaybeT (return m) + rhs: hoistMaybe m +- warn: + lhs: MaybeT . pure + rhs: hoistMaybe +- warn: + lhs: MaybeT . return + rhs: hoistMaybe +- warn: + lhs: ExceptT (pure m) + rhs: hoistEither m +- warn: + lhs: ExceptT (return m) + rhs: hoistEither m +- warn: + lhs: ExceptT . pure + rhs: hoistEither +- warn: + lhs: ExceptT . return + rhs: hoistEither +- warn: + lhs: fromMaybe mempty + rhs: maybeToMonoid +- warn: + lhs: 'm ?: mempty' + rhs: maybeToMonoid m +- warn: + lhs: Data.Map.toAscList (Data.Map.fromList x) + rhs: sortWith fst x +- warn: + lhs: Data.Map.toDescList (Data.Map.fromList x) + rhs: sortWith (Down . fst) x +- warn: + lhs: Data.Set.toList (Data.Set.fromList l) + rhs: sortNub l +- warn: + lhs: Data.Set.assocs (Data.Set.fromList l) + rhs: sortNub l +- warn: + lhs: Data.Set.toAscList (Data.Set.fromList l) + rhs: sortNub l +- warn: + lhs: Data.HashSet.toList (Data.HashSet.fromList l) + rhs: unstableNub l +- warn: + lhs: nub + note: '''nub'' is O(n^2), ''ordNub'' is O(n log n)' + rhs: ordNub +- warn: + lhs: sortBy (comparing 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. + rhs: sortWith f +- warn: + lhs: sortOn fst + note: '''sortWith'' will be faster here because it doesn''t do caching' + rhs: sortWith fst +- warn: + lhs: sortOn snd + note: '''sortWith'' will be faster here because it doesn''t do caching' + rhs: sortWith snd +- warn: + lhs: sortOn (Down . fst) + note: '''sortWith'' will be faster here because it doesn''t do caching' + rhs: sortWith (Down . fst) +- warn: + lhs: sortOn (Down . snd) + note: '''sortWith'' will be faster here because it doesn''t do caching' + rhs: sortWith (Down . snd) +- warn: + lhs: Data.Text.IO.putStr + rhs: putText +- warn: + lhs: Data.Text.IO.putStrLn + rhs: putTextLn +- warn: + lhs: Data.Text.Lazy.IO.putStr + rhs: putLText +- warn: + lhs: Data.Text.Lazy.IO.putStrLn + rhs: putLTextLn +- warn: + lhs: Data.ByteString.Char8.putStr + rhs: putBS +- warn: + lhs: Data.ByteString.Char8.putStrLn + rhs: putBSLn +- warn: + lhs: Data.ByteString.Lazy.Char8.putStr + rhs: putLBS +- warn: + lhs: Data.ByteString.Lazy.Char8.putStrLn + rhs: putLBSLn +- warn: + lhs: Data.Text.Lazy.Text + rhs: LText +- warn: + lhs: Data.ByteString.Lazy.ByteString + rhs: LByteString +- warn: + lhs: Data.ByteString.UTF8.fromString + rhs: encodeUtf8 +- warn: + lhs: Data.ByteString.UTF8.toString + rhs: decodeUtf8 +- warn: + lhs: Data.Text.Encoding.encodeUtf8 + rhs: encodeUtf8 +- warn: + lhs: Data.Text.Encoding.decodeUtf8 + rhs: decodeUtf8 +- warn: + lhs: Data.ByteString.Lazy.toStrict (encodeUtf8 x) + rhs: encodeUtf8 x +- warn: + lhs: toStrict (encodeUtf8 x) + rhs: encodeUtf8 x +- warn: + lhs: decodeUtf8 (Data.ByteString.Lazy.fromStrict x) + rhs: decodeUtf8 x +- warn: + lhs: decodeUtf8 (fromStrict x) + rhs: decodeUtf8 x +- warn: + lhs: Data.ByteString.Lazy.UTF8.fromString + rhs: encodeUtf8 +- warn: + lhs: Data.ByteString.Lazy.UTF8.toString + rhs: decodeUtf8 +- warn: + lhs: Data.ByteString.Lazy.fromStrict (Data.Text.Encoding.encodeUtf8 x) + rhs: encodeUtf8 x +- warn: + lhs: Data.ByteString.Lazy.fromStrict (encodeUtf8 x) + rhs: encodeUtf8 x +- warn: + lhs: Data.Text.Encoding.decodeUtf8 (Data.ByteString.Lazy.toStrict x) + rhs: decodeUtf8 x +- warn: + lhs: Data.Text.Encoding.decodeUtf8 (toStrict x) + rhs: decodeUtf8 x +- warn: + lhs: decodeUtf8 (Data.ByteString.Lazy.toStrict x) + rhs: decodeUtf8 x +- warn: + lhs: decodeUtf8 (toStrict x) + rhs: decodeUtf8 x +- warn: + lhs: Data.Text.pack + rhs: toText +- warn: + lhs: Data.Text.unpack + rhs: toString +- warn: + lhs: Data.Text.Lazy.pack + rhs: toLText +- warn: + lhs: Data.Text.Lazy.unpack + rhs: toString +- warn: + lhs: Data.Text.Lazy.toStrict + rhs: toText +- warn: + lhs: Data.Text.Lazy.fromStrict + rhs: toLText +- warn: + lhs: Data.Text.pack (show x) + rhs: show x +- warn: + lhs: Data.Text.Lazy.pack (show x) + rhs: show x +- warn: + lhs: Data.ByteString.Lazy.fromStrict + rhs: fromStrict +- warn: + lhs: Data.ByteString.Lazy.toStrict + rhs: toStrict +- warn: + lhs: Data.Text.Lazy.fromStrict + rhs: fromStrict +- warn: + lhs: Data.Text.Lazy.toStrict + rhs: toStrict +- warn: + lhs: Control.Applicative.Alternative + note: '''Alternative'' is already exported from Relude' + name: Use 'Alternative' from Relude + rhs: Alternative +- warn: + lhs: Control.Applicative.empty + note: '''empty'' is already exported from Relude' + name: Use 'empty' from Relude + rhs: empty +- warn: + lhs: (Control.Applicative.<|>) + note: Operator '(<|>)' is already exported from Relude + name: Use '<|>' from Relude + rhs: (<|>) +- warn: + lhs: Control.Applicative.some + note: '''some'' is already exported from Relude' + name: Use 'some' from Relude + rhs: some +- warn: + lhs: Control.Applicative.many + note: '''many'' is already exported from Relude' + name: Use 'many' from Relude + rhs: many +- warn: + lhs: Control.Applicative.Const + note: '''Const'' is already exported from Relude' + name: Use 'Const' from Relude + rhs: Const +- warn: + lhs: Control.Applicative.getConst + note: '''getConst'' is already exported from Relude' + name: Use 'getConst' from Relude + rhs: getConst +- warn: + lhs: Control.Applicative.ZipList + note: '''ZipList'' is already exported from Relude' + name: Use 'ZipList' from Relude + rhs: ZipList +- warn: + lhs: Control.Applicative.getZipList + note: '''getZipList'' is already exported from Relude' + name: Use 'getZipList' from Relude + rhs: getZipList +- warn: + lhs: Control.Applicative.liftA2 + note: '''liftA2'' is already exported from Relude' + name: Use 'liftA2' from Relude + rhs: liftA2 +- warn: + lhs: Control.Applicative.liftA3 + note: '''liftA3'' is already exported from Relude' + name: Use 'liftA3' from Relude + rhs: liftA3 +- warn: + lhs: Control.Applicative.optional + note: '''optional'' is already exported from Relude' + name: Use 'optional' from Relude + rhs: optional +- warn: + lhs: (Control.Applicative.<**>) + note: Operator '(<**>)' is already exported from Relude + name: Use '<**>' from Relude + rhs: (<**>) +- warn: + lhs: Data.Bits.xor + note: '''xor'' is already exported from Relude' + name: Use 'xor' from Relude + rhs: xor +- warn: + lhs: Data.Char.chr + note: '''chr'' is already exported from Relude' + name: Use 'chr' from Relude + rhs: chr +- warn: + lhs: Data.Int.Int8 + note: '''Int8'' is already exported from Relude' + name: Use 'Int8' from Relude + rhs: Int8 +- warn: + lhs: Data.Int.Int16 + note: '''Int16'' is already exported from Relude' + name: Use 'Int16' from Relude + rhs: Int16 +- warn: + lhs: Data.Int.Int32 + note: '''Int32'' is already exported from Relude' + name: Use 'Int32' from Relude + rhs: Int32 +- warn: + lhs: Data.Int.Int64 + note: '''Int64'' is already exported from Relude' + name: Use 'Int64' from Relude + rhs: Int64 +- warn: + lhs: Data.Word.Word8 + note: '''Word8'' is already exported from Relude' + name: Use 'Word8' from Relude + rhs: Word8 +- warn: + lhs: Data.Word.Word16 + note: '''Word16'' is already exported from Relude' + name: Use 'Word16' from Relude + rhs: Word16 +- warn: + lhs: Data.Word.Word32 + note: '''Word32'' is already exported from Relude' + name: Use 'Word32' from Relude + rhs: Word32 +- warn: + lhs: Data.Word.Word64 + note: '''Word64'' is already exported from Relude' + name: Use 'Word64' from Relude + rhs: Word64 +- warn: + lhs: Data.Word.byteSwap16 + note: '''byteSwap16'' is already exported from Relude' + name: Use 'byteSwap16' from Relude + rhs: byteSwap16 +- warn: + lhs: Data.Word.byteSwap32 + note: '''byteSwap32'' is already exported from Relude' + name: Use 'byteSwap32' from Relude + rhs: byteSwap32 +- warn: + lhs: Data.Word.byteSwap64 + note: '''byteSwap64'' is already exported from Relude' + name: Use 'byteSwap64' from Relude + rhs: byteSwap64 +- warn: + lhs: Numeric.Natural.Natural + note: '''Natural'' is already exported from Relude' + name: Use 'Natural' from Relude + rhs: Natural +- warn: + lhs: System.IO.Handle + note: '''Handle'' is already exported from Relude' + name: Use 'Handle' from Relude + rhs: Handle +- warn: + lhs: System.IO.IOMode + note: '''IOMode'' is already exported from Relude' + name: Use 'IOMode' from Relude + rhs: IOMode +- warn: + lhs: System.IO.ReadMode + note: '''ReadMode'' is already exported from Relude' + name: Use 'ReadMode' from Relude + rhs: ReadMode +- warn: + lhs: System.IO.WriteMode + note: '''WriteMode'' is already exported from Relude' + name: Use 'WriteMode' from Relude + rhs: WriteMode +- warn: + lhs: System.IO.AppendMode + note: '''AppendMode'' is already exported from Relude' + name: Use 'AppendMode' from Relude + rhs: AppendMode +- warn: + lhs: System.IO.ReadWriteMode + note: '''ReadWriteMode'' is already exported from Relude' + name: Use 'ReadWriteMode' from Relude + rhs: ReadWriteMode +- warn: + lhs: System.IO.stderr + note: '''stderr'' is already exported from Relude' + name: Use 'stderr' from Relude + rhs: stderr +- warn: + lhs: System.IO.stdin + note: '''stdin'' is already exported from Relude' + name: Use 'stdin' from Relude + rhs: stdin +- warn: + lhs: System.IO.stdout + note: '''stdout'' is already exported from Relude' + name: Use 'stdout' from Relude + rhs: stdout +- warn: + lhs: System.IO.withFile + note: '''withFile'' is already exported from Relude' + name: Use 'withFile' from Relude + rhs: withFile +- warn: + lhs: Data.Ord.Down + note: '''Down'' is already exported from Relude' + name: Use 'Down' from Relude + rhs: Down +- warn: + lhs: Data.Ord.comparing + note: '''comparing'' is already exported from Relude' + name: Use 'comparing' from Relude + rhs: comparing +- warn: + lhs: Data.Coerce.Coercible + note: '''Coercible'' is already exported from Relude' + name: Use 'Coercible' from Relude + rhs: Coercible +- warn: + lhs: Data.Coerce.coerce + note: '''coerce'' is already exported from Relude' + name: Use 'coerce' from Relude + rhs: coerce +- warn: + lhs: Data.Kind.Constraint + note: '''Constraint'' is already exported from Relude' + name: Use 'Constraint' from Relude + rhs: Constraint +- warn: + lhs: Data.Kind.Type + note: '''Type'' is already exported from Relude' + name: Use 'Type' from Relude + rhs: Type +- warn: + lhs: Data.Typeable.Typeable + note: '''Typeable'' is already exported from Relude' + name: Use 'Typeable' from Relude + rhs: Typeable +- warn: + lhs: Data.Proxy.Proxy + note: '''Proxy'' is already exported from Relude' + name: Use 'Proxy' from Relude + rhs: Proxy +- warn: + lhs: Data.Typeable.Typeable + note: '''Typeable'' is already exported from Relude' + name: Use 'Typeable' from Relude + rhs: Typeable +- warn: + lhs: Data.Void.Void + note: '''Void'' is already exported from Relude' + name: Use 'Void' from Relude + rhs: Void +- warn: + lhs: Data.Void.absurd + note: '''absurd'' is already exported from Relude' + name: Use 'absurd' from Relude + rhs: absurd +- warn: + lhs: Data.Void.vacuous + note: '''vacuous'' is already exported from Relude' + name: Use 'vacuous' from Relude + rhs: vacuous +- warn: + lhs: Data.Base.maxInt + note: '''maxInt'' is already exported from Relude' + name: Use 'maxInt' from Relude + rhs: maxInt +- warn: + lhs: Data.Base.minInt + note: '''minInt'' is already exported from Relude' + name: Use 'minInt' from Relude + rhs: minInt +- warn: + lhs: Data.Base.ord + note: '''ord'' is already exported from Relude' + name: Use 'ord' from Relude + rhs: ord +- warn: + lhs: GHC.Enum.boundedEnumFrom + note: '''boundedEnumFrom'' is already exported from Relude' + name: Use 'boundedEnumFrom' from Relude + rhs: boundedEnumFrom +- warn: + lhs: GHC.Enum.boundedEnumFromThen + note: '''boundedEnumFromThen'' is already exported from Relude' + name: Use 'boundedEnumFromThen' from Relude + rhs: boundedEnumFromThen +- warn: + lhs: GHC.Generics.Generic + note: '''Generic'' is already exported from Relude' + name: Use 'Generic' from Relude + rhs: Generic +- warn: + lhs: GHC.Real.Ratio + note: '''Ratio'' is already exported from Relude' + name: Use 'Ratio' from Relude + rhs: Ratio +- warn: + lhs: GHC.Real.Rational + note: '''Rational'' is already exported from Relude' + name: Use 'Rational' from Relude + rhs: Rational +- warn: + lhs: GHC.Real.denominator + note: '''denominator'' is already exported from Relude' + name: Use 'denominator' from Relude + rhs: denominator +- warn: + lhs: GHC.Real.numerator + note: '''numerator'' is already exported from Relude' + name: Use 'numerator' from Relude + rhs: numerator +- warn: + lhs: GHC.TypeNats.CmpNat + note: '''CmpNat'' is already exported from Relude' + name: Use 'CmpNat' from Relude + rhs: CmpNat +- warn: + lhs: GHC.TypeNats.KnownNat + note: '''KnownNat'' is already exported from Relude' + name: Use 'KnownNat' from Relude + rhs: KnownNat +- warn: + lhs: GHC.TypeNats.Nat + note: '''Nat'' is already exported from Relude' + name: Use 'Nat' from Relude + rhs: Nat +- warn: + lhs: GHC.TypeNats.SomeNat + note: '''SomeNat'' is already exported from Relude' + name: Use 'SomeNat' from Relude + rhs: SomeNat +- warn: + lhs: GHC.TypeNats.natVal + note: '''natVal'' is already exported from Relude' + name: Use 'natVal' from Relude + rhs: natVal +- warn: + lhs: GHC.TypeNats.someNatVal + note: '''someNatVal'' is already exported from Relude' + name: Use 'someNatVal' from Relude + rhs: someNatVal +- warn: + lhs: GHC.TypeLits.CmpNat + note: '''CmpNat'' is already exported from Relude' + name: Use 'CmpNat' from Relude + rhs: CmpNat +- warn: + lhs: GHC.TypeLits.KnownNat + note: '''KnownNat'' is already exported from Relude' + name: Use 'KnownNat' from Relude + rhs: KnownNat +- warn: + lhs: GHC.TypeLits.Nat + note: '''Nat'' is already exported from Relude' + name: Use 'Nat' from Relude + rhs: Nat +- warn: + lhs: GHC.TypeLits.SomeNat + note: '''SomeNat'' is already exported from Relude' + name: Use 'SomeNat' from Relude + rhs: SomeNat +- warn: + lhs: GHC.TypeLits.natVal + note: '''natVal'' is already exported from Relude' + name: Use 'natVal' from Relude + rhs: natVal +- warn: + lhs: GHC.TypeLits.someNatVal + note: '''someNatVal'' is already exported from Relude' + name: Use 'someNatVal' from Relude + rhs: someNatVal +- warn: + lhs: GHC.ExecutionStack.getStackTrace + note: '''getStackTrace'' is already exported from Relude' + name: Use 'getStackTrace' from Relude + rhs: getStackTrace +- warn: + lhs: GHC.ExecutionStack.showStackTrace + note: '''showStackTrace'' is already exported from Relude' + name: Use 'showStackTrace' from Relude + rhs: showStackTrace +- warn: + lhs: GHC.OverloadedLabels.IsLabel + note: '''IsLabel'' is already exported from Relude' + name: Use 'IsLabel' from Relude + rhs: IsLabel +- warn: + lhs: GHC.OverloadedLabels.fromLabel + note: '''fromLabel'' is already exported from Relude' + name: Use 'fromLabel' from Relude + rhs: fromLabel +- warn: + lhs: GHC.Stack.CallStack + note: '''CallStack'' is already exported from Relude' + name: Use 'CallStack' from Relude + rhs: CallStack +- warn: + lhs: GHC.Stack.HasCallStack + note: '''HasCallStack'' is already exported from Relude' + name: Use 'HasCallStack' from Relude + rhs: HasCallStack +- warn: + lhs: GHC.Stack.callStack + note: '''callStack'' is already exported from Relude' + name: Use 'callStack' from Relude + rhs: callStack +- warn: + lhs: GHC.Stack.currentCallStack + note: '''currentCallStack'' is already exported from Relude' + name: Use 'currentCallStack' from Relude + rhs: currentCallStack +- warn: + lhs: GHC.Stack.getCallStack + note: '''getCallStack'' is already exported from Relude' + name: Use 'getCallStack' from Relude + rhs: getCallStack +- warn: + lhs: GHC.Stack.prettyCallStack + note: '''prettyCallStack'' is already exported from Relude' + name: Use 'prettyCallStack' from Relude + rhs: prettyCallStack +- warn: + lhs: GHC.Stack.prettySrcLoc + note: '''prettySrcLoc'' is already exported from Relude' + name: Use 'prettySrcLoc' from Relude + rhs: prettySrcLoc +- warn: + lhs: GHC.Stack.withFrozenCallStack + note: '''withFrozenCallStack'' is already exported from Relude' + name: Use 'withFrozenCallStack' from Relude + rhs: withFrozenCallStack +- warn: + lhs: Data.Bifoldable.Bifoldable + note: '''Bifoldable'' is already exported from Relude' + name: Use 'Bifoldable' from Relude + rhs: Bifoldable +- warn: + lhs: Data.Bifoldable.bifold + note: '''bifold'' is already exported from Relude' + name: Use 'bifold' from Relude + rhs: bifold +- warn: + lhs: Data.Bifoldable.bifoldMap + note: '''bifoldMap'' is already exported from Relude' + name: Use 'bifoldMap' from Relude + rhs: bifoldMap +- warn: + lhs: Data.Bifoldable.bifoldr + note: '''bifoldr'' is already exported from Relude' + name: Use 'bifoldr' from Relude + rhs: bifoldr +- warn: + lhs: Data.Bifoldable.bifoldl + note: '''bifoldl'' is already exported from Relude' + name: Use 'bifoldl' from Relude + rhs: bifoldl +- warn: + lhs: Data.Bifoldable.bifoldl' + note: '''bifoldl'''' is already exported from Relude' + name: Use 'bifoldl'' from Relude + rhs: bifoldl' +- warn: + lhs: Data.Bifoldable.bifoldlM + note: '''bifoldlM'' is already exported from Relude' + name: Use 'bifoldlM' from Relude + rhs: bifoldlM +- warn: + lhs: Data.Bifoldable.bifoldr' + note: '''bifoldr'''' is already exported from Relude' + name: Use 'bifoldr'' from Relude + rhs: bifoldr' +- warn: + lhs: Data.Bifoldable.bifoldrM + note: '''bifoldrM'' is already exported from Relude' + name: Use 'bifoldrM' from Relude + rhs: bifoldrM +- warn: + lhs: Data.Bifoldable.bitraverse_ + note: '''bitraverse_'' is already exported from Relude' + name: Use 'bitraverse_' from Relude + rhs: bitraverse_ +- warn: + lhs: Data.Bifoldable.bifor_ + note: '''bifor_'' is already exported from Relude' + name: Use 'bifor_' from Relude + rhs: bifor_ +- warn: + lhs: Data.Bifoldable.biasum + note: '''biasum'' is already exported from Relude' + name: Use 'biasum' from Relude + rhs: biasum +- warn: + lhs: Data.Bifoldable.bisequence_ + note: '''bisequence_'' is already exported from Relude' + name: Use 'bisequence_' from Relude + rhs: bisequence_ +- warn: + lhs: Data.Bifoldable.biList + note: '''biList'' is already exported from Relude' + name: Use 'biList' from Relude + rhs: biList +- warn: + lhs: Data.Bifoldable.binull + note: '''binull'' is already exported from Relude' + name: Use 'binull' from Relude + rhs: binull +- warn: + lhs: Data.Bifoldable.bilength + note: '''bilength'' is already exported from Relude' + name: Use 'bilength' from Relude + rhs: bilength +- warn: + lhs: Data.Bifoldable.bielem + note: '''bielem'' is already exported from Relude' + name: Use 'bielem' from Relude + rhs: bielem +- warn: + lhs: Data.Bifoldable.biand + note: '''biand'' is already exported from Relude' + name: Use 'biand' from Relude + rhs: biand +- warn: + lhs: Data.Bifoldable.bior + note: '''bior'' is already exported from Relude' + name: Use 'bior' from Relude + rhs: bior +- warn: + lhs: Data.Bifoldable.biany + note: '''biany'' is already exported from Relude' + name: Use 'biany' from Relude + rhs: biany +- warn: + lhs: Data.Bifoldable.biall + note: '''biall'' is already exported from Relude' + name: Use 'biall' from Relude + rhs: biall +- warn: + lhs: Data.Bifoldable.bifind + note: '''bifind'' is already exported from Relude' + name: Use 'bifind' from Relude + rhs: bifind +- warn: + lhs: Data.Bitraversable.Bitraversable + note: '''Bitraversable'' is already exported from Relude' + name: Use 'Bitraversable' from Relude + rhs: Bitraversable +- warn: + lhs: Data.Bitraversable.bitraverse + note: '''bitraverse'' is already exported from Relude' + name: Use 'bitraverse' from Relude + rhs: bitraverse +- warn: + lhs: Data.Bitraversable.bisequence + note: '''bisequence'' is already exported from Relude' + name: Use 'bisequence' from Relude + rhs: bisequence +- warn: + lhs: Data.Bitraversable.bifor + note: '''bifor'' is already exported from Relude' + name: Use 'bifor' from Relude + rhs: bifor +- warn: + lhs: Data.Bitraversable.bimapDefault + note: '''bimapDefault'' is already exported from Relude' + name: Use 'bimapDefault' from Relude + rhs: bimapDefault +- warn: + lhs: Data.Bitraversable.bifoldMapDefault + note: '''bifoldMapDefault'' is already exported from Relude' + name: Use 'bifoldMapDefault' from Relude + rhs: bifoldMapDefault +- warn: + lhs: Control.Monad.guard + note: '''guard'' is already exported from Relude' + name: Use 'guard' from Relude + rhs: guard +- warn: + lhs: Control.Monad.unless + note: '''unless'' is already exported from Relude' + name: Use 'unless' from Relude + rhs: unless +- warn: + lhs: Control.Monad.when + note: '''when'' is already exported from Relude' + name: Use 'when' from Relude + rhs: when +- warn: + lhs: Data.Bool.bool + note: '''bool'' is already exported from Relude' + name: Use 'bool' from Relude + rhs: bool +- warn: + lhs: Data.Hashable.Hashable + note: '''Hashable'' is already exported from Relude' + name: Use 'Hashable' from Relude + rhs: Hashable +- warn: + lhs: Data.Hashable.hashWithSalt + note: '''hashWithSalt'' is already exported from Relude' + name: Use 'hashWithSalt' from Relude + rhs: hashWithSalt +- warn: + lhs: Data.HashMap.Strict.HashMap + note: '''HashMap'' is already exported from Relude' + name: Use 'HashMap' from Relude + rhs: HashMap +- warn: + lhs: Data.HashSet.HashSet + note: '''HashSet'' is already exported from Relude' + name: Use 'HashSet' from Relude + rhs: HashSet +- warn: + lhs: Data.IntMap.Strict.IntMap + note: '''IntMap'' is already exported from Relude' + name: Use 'IntMap' from Relude + rhs: IntMap +- warn: + lhs: Data.IntSet.IntSet + note: '''IntSet'' is already exported from Relude' + name: Use 'IntSet' from Relude + rhs: IntSet +- warn: + lhs: Data.Map.Strict.Map + note: '''Map'' is already exported from Relude' + name: Use 'Map' from Relude + rhs: Map +- warn: + lhs: Data.Sequence.Sequence + note: '''Sequence'' is already exported from Relude' + name: Use 'Sequence' from Relude + rhs: Sequence +- warn: + lhs: Data.Set.Set + note: '''Set'' is already exported from Relude' + name: Use 'Set' from Relude + rhs: Set +- warn: + lhs: Data.Tuple.swap + note: '''swap'' is already exported from Relude' + name: Use 'swap' from Relude + rhs: swap +- warn: + lhs: Data.Vector.Vector + note: '''Vector'' is already exported from Relude' + name: Use 'Vector' from Relude + rhs: Vector +- warn: + lhs: GHC.Exts.IsList + note: '''IsList'' is already exported from Relude' + name: Use 'IsList' from Relude + rhs: IsList +- warn: + lhs: GHC.Exts.fromList + note: '''fromList'' is already exported from Relude' + name: Use 'fromList' from Relude + rhs: fromList +- warn: + lhs: GHC.Exts.fromListN + note: '''fromListN'' is already exported from Relude' + name: Use 'fromListN' from Relude + rhs: fromListN +- warn: + lhs: Debug.Trace.trace + note: '''trace'' is already exported from Relude' + name: Use 'trace' from Relude + rhs: trace +- warn: + lhs: Debug.Trace.traceShow + note: '''traceShow'' is already exported from Relude' + name: Use 'traceShow' from Relude + rhs: traceShow +- warn: + lhs: Debug.Trace.traceShowId + note: '''traceShowId'' is already exported from Relude' + name: Use 'traceShowId' from Relude + rhs: traceShowId +- warn: + lhs: Debug.Trace.traceShowM + note: '''traceShowM'' is already exported from Relude' + name: Use 'traceShowM' from Relude + rhs: traceShowM +- warn: + lhs: Debug.Trace.traceM + note: '''traceM'' is already exported from Relude' + name: Use 'traceM' from Relude + rhs: traceM +- warn: + lhs: Debug.Trace.traceId + note: '''traceId'' is already exported from Relude' + name: Use 'traceId' from Relude + rhs: traceId +- warn: + lhs: Control.DeepSeq.NFData + note: '''NFData'' is already exported from Relude' + name: Use 'NFData' from Relude + rhs: NFData +- warn: + lhs: Control.DeepSeq.rnf + note: '''rnf'' is already exported from Relude' + name: Use 'rnf' from Relude + rhs: rnf +- warn: + lhs: Control.DeepSeq.deepseq + note: '''deepseq'' is already exported from Relude' + name: Use 'deepseq' from Relude + rhs: deepseq +- warn: + lhs: Control.DeepSeq.force + note: '''force'' is already exported from Relude' + name: Use 'force' from Relude + rhs: force +- warn: + lhs: (Control.DeepSeq.$!!) + note: Operator '($!!)' is already exported from Relude + name: Use '$!!' from Relude + rhs: ($!!) +- warn: + lhs: Control.Exception.Exception + note: '''Exception'' is already exported from Relude' + name: Use 'Exception' from Relude + rhs: Exception +- warn: + lhs: Control.Exception.SomeException + note: '''SomeException'' is already exported from Relude' + name: Use 'SomeException' from Relude + rhs: SomeException +- warn: + lhs: Control.Exception.toException + note: '''toException'' is already exported from Relude' + name: Use 'toException' from Relude + rhs: toException +- warn: + lhs: Control.Exception.fromException + note: '''fromException'' is already exported from Relude' + name: Use 'fromException' from Relude + rhs: fromException +- warn: + lhs: Control.Exception.displayException + note: '''displayException'' is already exported from Relude' + name: Use 'displayException' from Relude + rhs: displayException +- warn: + lhs: Data.Foldable.asum + note: '''asum'' is already exported from Relude' + name: Use 'asum' from Relude + rhs: asum +- warn: + lhs: Data.Foldable.find + note: '''find'' is already exported from Relude' + name: Use 'find' from Relude + rhs: find +- warn: + lhs: Data.Foldable.find + note: '''find'' is already exported from Relude' + name: Use 'find' from Relude + rhs: find +- warn: + lhs: Data.Foldable.fold + note: '''fold'' is already exported from Relude' + name: Use 'fold' from Relude + rhs: fold +- warn: + lhs: Data.Foldable.foldl' + note: '''foldl'''' is already exported from Relude' + name: Use 'foldl'' from Relude + rhs: foldl' +- warn: + lhs: Data.Foldable.foldrM + note: '''foldrM'' is already exported from Relude' + name: Use 'foldrM' from Relude + rhs: foldrM +- warn: + lhs: Data.Foldable.forM_ + note: '''forM_'' is already exported from Relude' + name: Use 'forM_' from Relude + rhs: forM_ +- warn: + lhs: Data.Foldable.for_ + note: '''for_'' is already exported from Relude' + name: Use 'for_' from Relude + rhs: for_ +- warn: + lhs: Data.Foldable.sequenceA_ + note: '''sequenceA_'' is already exported from Relude' + name: Use 'sequenceA_' from Relude + rhs: sequenceA_ +- warn: + lhs: Data.Foldable.toList + note: '''toList'' is already exported from Relude' + name: Use 'toList' from Relude + rhs: toList +- warn: + lhs: Data.Foldable.traverse_ + note: '''traverse_'' is already exported from Relude' + name: Use 'traverse_' from Relude + rhs: traverse_ +- warn: + lhs: Data.Traversable.forM + note: '''forM'' is already exported from Relude' + name: Use 'forM' from Relude + rhs: forM +- warn: + lhs: Data.Traversable.mapAccumL + note: '''mapAccumL'' is already exported from Relude' + name: Use 'mapAccumL' from Relude + rhs: mapAccumL +- warn: + lhs: Data.Traversable.mapAccumR + note: '''mapAccumR'' is already exported from Relude' + name: Use 'mapAccumR' from Relude + rhs: mapAccumR +- warn: + lhs: (Control.Arrow.&&&) + note: Operator '(&&&)' is already exported from Relude + name: Use '&&&' from Relude + rhs: (&&&) +- warn: + lhs: (Control.Category.>>>) + note: Operator '(>>>)' is already exported from Relude + name: Use '>>>' from Relude + rhs: (>>>) +- warn: + lhs: (Control.Category.<<<) + note: Operator '(<<<)' is already exported from Relude + name: Use '<<<' from Relude + rhs: (<<<) +- warn: + lhs: Data.Function.fix + note: '''fix'' is already exported from Relude' + name: Use 'fix' from Relude + rhs: fix +- warn: + lhs: Data.Function.on + note: '''on'' is already exported from Relude' + name: Use 'on' from Relude + rhs: 'on' +- warn: + lhs: Data.Bifunctor.Bifunctor + note: '''Bifunctor'' is already exported from Relude' + name: Use 'Bifunctor' from Relude + rhs: Bifunctor +- warn: + lhs: Data.Bifunctor.bimap + note: '''bimap'' is already exported from Relude' + name: Use 'bimap' from Relude + rhs: bimap +- warn: + lhs: Data.Bifunctor.first + note: '''first'' is already exported from Relude' + name: Use 'first' from Relude + rhs: first +- warn: + lhs: Data.Bifunctor.second + note: '''second'' is already exported from Relude' + name: Use 'second' from Relude + rhs: second +- warn: + lhs: Data.Functor.void + note: '''void'' is already exported from Relude' + name: Use 'void' from Relude + rhs: void +- warn: + lhs: (Data.Functor.$>) + note: Operator '($>)' is already exported from Relude + name: Use '$>' from Relude + rhs: ($>) +- warn: + lhs: (Data.Functor.<&>) + note: Operator '(<&>)' is already exported from Relude + name: Use '<&>' from Relude + rhs: (<&>) +- warn: + lhs: Data.Functor.Compose.Compose + note: '''Compose'' is already exported from Relude' + name: Use 'Compose' from Relude + rhs: Compose +- warn: + lhs: Data.Functor.Compose.getCompose + note: '''getCompose'' is already exported from Relude' + name: Use 'getCompose' from Relude + rhs: getCompose +- warn: + lhs: Data.Functor.Identity.Identity + note: '''Identity'' is already exported from Relude' + name: Use 'Identity' from Relude + rhs: Identity +- warn: + lhs: Data.Functor.Identity.runIdentity + note: '''runIdentity'' is already exported from Relude' + name: Use 'runIdentity' from Relude + rhs: runIdentity +- warn: + lhs: Control.Concurrent.MVar.MVar + note: '''MVar'' is already exported from Relude' + name: Use 'MVar' from Relude + rhs: MVar +- warn: + lhs: Control.Concurrent.MVar.newEmptyMVar + note: '''newEmptyMVar'' is already exported from Relude' + name: Use 'newEmptyMVar' from Relude + rhs: newEmptyMVar +- warn: + lhs: Control.Concurrent.MVar.newMVar + note: '''newMVar'' is already exported from Relude' + name: Use 'newMVar' from Relude + rhs: newMVar +- warn: + lhs: Control.Concurrent.MVar.putMVar + note: '''putMVar'' is already exported from Relude' + name: Use 'putMVar' from Relude + rhs: putMVar +- warn: + lhs: Control.Concurrent.MVar.readMVar + note: '''readMVar'' is already exported from Relude' + name: Use 'readMVar' from Relude + rhs: readMVar +- warn: + lhs: Control.Concurrent.MVar.swapMVar + note: '''swapMVar'' is already exported from Relude' + name: Use 'swapMVar' from Relude + rhs: swapMVar +- warn: + lhs: Control.Concurrent.MVar.takeMVar + note: '''takeMVar'' is already exported from Relude' + name: Use 'takeMVar' from Relude + rhs: takeMVar +- warn: + lhs: Control.Concurrent.MVar.tryPutMVar + note: '''tryPutMVar'' is already exported from Relude' + name: Use 'tryPutMVar' from Relude + rhs: tryPutMVar +- warn: + lhs: Control.Concurrent.MVar.tryReadMVar + note: '''tryReadMVar'' is already exported from Relude' + name: Use 'tryReadMVar' from Relude + rhs: tryReadMVar +- warn: + lhs: Control.Concurrent.MVar.tryTakeMVar + note: '''tryTakeMVar'' is already exported from Relude' + name: Use 'tryTakeMVar' from Relude + rhs: tryTakeMVar +- warn: + lhs: Control.Monad.STM.STM + note: '''STM'' is already exported from Relude' + name: Use 'STM' from Relude + rhs: STM +- warn: + lhs: Control.Monad.STM.atomically + note: '''atomically'' is already exported from Relude' + name: Use 'atomically' from Relude + rhs: atomically +- warn: + lhs: Control.Concurrent.STM.TVar.TVar + note: '''TVar'' is already exported from Relude' + name: Use 'TVar' from Relude + rhs: TVar +- warn: + lhs: Control.Concurrent.STM.TVar.newTVarIO + note: '''newTVarIO'' is already exported from Relude' + name: Use 'newTVarIO' from Relude + rhs: newTVarIO +- warn: + lhs: Control.Concurrent.STM.TVar.readTVarIO + note: '''readTVarIO'' is already exported from Relude' + name: Use 'readTVarIO' from Relude + rhs: readTVarIO +- warn: + lhs: Control.Concurrent.STM.TVar.modifyTVar' + note: '''modifyTVar'''' is already exported from Relude' + name: Use 'modifyTVar'' from Relude + rhs: modifyTVar' +- warn: + lhs: Control.Concurrent.STM.TVar.newTVar + note: '''newTVar'' is already exported from Relude' + name: Use 'newTVar' from Relude + rhs: newTVar +- warn: + lhs: Control.Concurrent.STM.TVar.readTVar + note: '''readTVar'' is already exported from Relude' + name: Use 'readTVar' from Relude + rhs: readTVar +- warn: + lhs: Control.Concurrent.STM.TVar.writeTVar + note: '''writeTVar'' is already exported from Relude' + name: Use 'writeTVar' from Relude + rhs: writeTVar +- warn: + lhs: Data.IORef.IORef + note: '''IORef'' is already exported from Relude' + name: Use 'IORef' from Relude + rhs: IORef +- warn: + lhs: Data.IORef.atomicModifyIORef + note: '''atomicModifyIORef'' is already exported from Relude' + name: Use 'atomicModifyIORef' from Relude + rhs: atomicModifyIORef +- warn: + lhs: Data.IORef.atomicModifyIORef' + note: '''atomicModifyIORef'''' is already exported from Relude' + name: Use 'atomicModifyIORef'' from Relude + rhs: atomicModifyIORef' +- warn: + lhs: Data.IORef.atomicWriteIORef + note: '''atomicWriteIORef'' is already exported from Relude' + name: Use 'atomicWriteIORef' from Relude + rhs: atomicWriteIORef +- warn: + lhs: Data.IORef.modifyIORef + note: '''modifyIORef'' is already exported from Relude' + name: Use 'modifyIORef' from Relude + rhs: modifyIORef +- warn: + lhs: Data.IORef.modifyIORef' + note: '''modifyIORef'''' is already exported from Relude' + name: Use 'modifyIORef'' from Relude + rhs: modifyIORef' +- warn: + lhs: Data.IORef.newIORef + note: '''newIORef'' is already exported from Relude' + name: Use 'newIORef' from Relude + rhs: newIORef +- warn: + lhs: Data.IORef.readIORef + note: '''readIORef'' is already exported from Relude' + name: Use 'readIORef' from Relude + rhs: readIORef +- warn: + lhs: Data.IORef.writeIORef + note: '''writeIORef'' is already exported from Relude' + name: Use 'writeIORef' from Relude + rhs: writeIORef +- warn: + lhs: Data.Text.IO.getLine + note: '''getLine'' is already exported from Relude' + name: Use 'getLine' from Relude + rhs: getLine +- warn: + lhs: Data.List.genericDrop + note: '''genericDrop'' is already exported from Relude' + name: Use 'genericDrop' from Relude + rhs: genericDrop +- warn: + lhs: Data.List.genericLength + note: '''genericLength'' is already exported from Relude' + name: Use 'genericLength' from Relude + rhs: genericLength +- warn: + lhs: Data.List.genericReplicate + note: '''genericReplicate'' is already exported from Relude' + name: Use 'genericReplicate' from Relude + rhs: genericReplicate +- warn: + lhs: Data.List.genericSplitAt + note: '''genericSplitAt'' is already exported from Relude' + name: Use 'genericSplitAt' from Relude + rhs: genericSplitAt +- warn: + lhs: Data.List.genericTake + note: '''genericTake'' is already exported from Relude' + name: Use 'genericTake' from Relude + rhs: genericTake +- warn: + lhs: Data.List.group + note: '''group'' is already exported from Relude' + name: Use 'group' from Relude + rhs: group +- warn: + lhs: Data.List.inits + note: '''inits'' is already exported from Relude' + name: Use 'inits' from Relude + rhs: inits +- warn: + lhs: Data.List.intercalate + note: '''intercalate'' is already exported from Relude' + name: Use 'intercalate' from Relude + rhs: intercalate +- warn: + lhs: Data.List.intersperse + note: '''intersperse'' is already exported from Relude' + name: Use 'intersperse' from Relude + rhs: intersperse +- warn: + lhs: Data.List.isPrefixOf + note: '''isPrefixOf'' is already exported from Relude' + name: Use 'isPrefixOf' from Relude + rhs: isPrefixOf +- warn: + lhs: Data.List.permutations + note: '''permutations'' is already exported from Relude' + name: Use 'permutations' from Relude + rhs: permutations +- warn: + lhs: Data.List.sort + note: '''sort'' is already exported from Relude' + name: Use 'sort' from Relude + rhs: sort +- warn: + lhs: Data.List.sortBy + note: '''sortBy'' is already exported from Relude' + name: Use 'sortBy' from Relude + rhs: sortBy +- warn: + lhs: Data.List.sortOn + note: '''sortOn'' is already exported from Relude' + name: Use 'sortOn' from Relude + rhs: sortOn +- warn: + lhs: Data.List.subsequences + note: '''subsequences'' is already exported from Relude' + name: Use 'subsequences' from Relude + rhs: subsequences +- warn: + lhs: Data.List.tails + note: '''tails'' is already exported from Relude' + name: Use 'tails' from Relude + rhs: tails +- warn: + lhs: Data.List.transpose + note: '''transpose'' is already exported from Relude' + name: Use 'transpose' from Relude + rhs: transpose +- warn: + lhs: Data.List.uncons + note: '''uncons'' is already exported from Relude' + name: Use 'uncons' from Relude + rhs: uncons +- warn: + lhs: Data.List.unfoldr + note: '''unfoldr'' is already exported from Relude' + name: Use 'unfoldr' from Relude + rhs: unfoldr +- warn: + lhs: Data.List.NonEmpty.NonEmpty + note: '''NonEmpty'' is already exported from Relude' + name: Use 'NonEmpty' from Relude + rhs: NonEmpty +- warn: + lhs: (Data.List.NonEmpty.:|) + note: Operator '(:|)' is already exported from Relude + name: Use ':|' from Relude + rhs: (:|) +- warn: + lhs: Data.List.NonEmpty.nonEmpty + note: '''nonEmpty'' is already exported from Relude' + name: Use 'nonEmpty' from Relude + rhs: nonEmpty +- warn: + lhs: Data.List.NonEmpty.head + note: '''head'' is already exported from Relude' + name: Use 'head' from Relude + rhs: head +- warn: + lhs: Data.List.NonEmpty.init + note: '''init'' is already exported from Relude' + name: Use 'init' from Relude + rhs: init +- warn: + lhs: Data.List.NonEmpty.last + note: '''last'' is already exported from Relude' + name: Use 'last' from Relude + rhs: last +- warn: + lhs: Data.List.NonEmpty.tail + note: '''tail'' is already exported from Relude' + name: Use 'tail' from Relude + rhs: tail +- warn: + lhs: GHC.Exts.sortWith + note: '''sortWith'' is already exported from Relude' + name: Use 'sortWith' from Relude + rhs: sortWith +- warn: + lhs: Control.Monad.Except.ExceptT + note: '''ExceptT'' is already exported from Relude' + name: Use 'ExceptT' from Relude + rhs: ExceptT +- warn: + lhs: Control.Monad.Except.runExceptT + note: '''runExceptT'' is already exported from Relude' + name: Use 'runExceptT' from Relude + rhs: runExceptT +- warn: + lhs: Control.Monad.Reader.MonadReader + note: '''MonadReader'' is already exported from Relude' + name: Use 'MonadReader' from Relude + rhs: MonadReader +- warn: + lhs: Control.Monad.Reader.Reader + note: '''Reader'' is already exported from Relude' + name: Use 'Reader' from Relude + rhs: Reader +- warn: + lhs: Control.Monad.Reader.ReaderT + note: '''ReaderT'' is already exported from Relude' + name: Use 'ReaderT' from Relude + rhs: ReaderT +- warn: + lhs: Control.Monad.Reader.runReaderT + note: '''runReaderT'' is already exported from Relude' + name: Use 'runReaderT' from Relude + rhs: runReaderT +- warn: + lhs: Control.Monad.Reader.ask + note: '''ask'' is already exported from Relude' + name: Use 'ask' from Relude + rhs: ask +- warn: + lhs: Control.Monad.Reader.asks + note: '''asks'' is already exported from Relude' + name: Use 'asks' from Relude + rhs: asks +- warn: + lhs: Control.Monad.Reader.local + note: '''local'' is already exported from Relude' + name: Use 'local' from Relude + rhs: local +- warn: + lhs: Control.Monad.Reader.reader + note: '''reader'' is already exported from Relude' + name: Use 'reader' from Relude + rhs: reader +- warn: + lhs: Control.Monad.Reader.runReader + note: '''runReader'' is already exported from Relude' + name: Use 'runReader' from Relude + rhs: runReader +- warn: + lhs: Control.Monad.Reader.withReader + note: '''withReader'' is already exported from Relude' + name: Use 'withReader' from Relude + rhs: withReader +- warn: + lhs: Control.Monad.Reader.withReaderT + note: '''withReaderT'' is already exported from Relude' + name: Use 'withReaderT' from Relude + rhs: withReaderT +- warn: + lhs: Control.Monad.State.Strict.MonadState + note: '''MonadState'' is already exported from Relude' + name: Use 'MonadState' from Relude + rhs: MonadState +- warn: + lhs: Control.Monad.State.Strict.State + note: '''State'' is already exported from Relude' + name: Use 'State' from Relude + rhs: State +- warn: + lhs: Control.Monad.State.Strict.StateT + note: '''StateT'' is already exported from Relude' + name: Use 'StateT' from Relude + rhs: StateT +- warn: + lhs: Control.Monad.State.Strict.runStateT + note: '''runStateT'' is already exported from Relude' + name: Use 'runStateT' from Relude + rhs: runStateT +- warn: + lhs: Control.Monad.State.Strict.evalState + note: '''evalState'' is already exported from Relude' + name: Use 'evalState' from Relude + rhs: evalState +- warn: + lhs: Control.Monad.State.Strict.evalStateT + note: '''evalStateT'' is already exported from Relude' + name: Use 'evalStateT' from Relude + rhs: evalStateT +- warn: + lhs: Control.Monad.State.Strict.execState + note: '''execState'' is already exported from Relude' + name: Use 'execState' from Relude + rhs: execState +- warn: + lhs: Control.Monad.State.Strict.execStateT + note: '''execStateT'' is already exported from Relude' + name: Use 'execStateT' from Relude + rhs: execStateT +- warn: + lhs: Control.Monad.State.Strict.get + note: '''get'' is already exported from Relude' + name: Use 'get' from Relude + rhs: get +- warn: + lhs: Control.Monad.State.Strict.gets + note: '''gets'' is already exported from Relude' + name: Use 'gets' from Relude + rhs: gets +- warn: + lhs: Control.Monad.State.Strict.modify + note: '''modify'' is already exported from Relude' + name: Use 'modify' from Relude + rhs: modify +- warn: + lhs: Control.Monad.State.Strict.modify' + note: '''modify'''' is already exported from Relude' + name: Use 'modify'' from Relude + rhs: modify' +- warn: + lhs: Control.Monad.State.Strict.put + note: '''put'' is already exported from Relude' + name: Use 'put' from Relude + rhs: put +- warn: + lhs: Control.Monad.State.Strict.runState + note: '''runState'' is already exported from Relude' + name: Use 'runState' from Relude + rhs: runState +- warn: + lhs: Control.Monad.State.Strict.state + note: '''state'' is already exported from Relude' + name: Use 'state' from Relude + rhs: state +- warn: + lhs: Control.Monad.State.Strict.withState + note: '''withState'' is already exported from Relude' + name: Use 'withState' from Relude + rhs: withState +- warn: + lhs: Control.Monad.Trans.MonadIO + note: '''MonadIO'' is already exported from Relude' + name: Use 'MonadIO' from Relude + rhs: MonadIO +- warn: + lhs: Control.Monad.Trans.MonadTrans + note: '''MonadTrans'' is already exported from Relude' + name: Use 'MonadTrans' from Relude + rhs: MonadTrans +- warn: + lhs: Control.Monad.Trans.lift + note: '''lift'' is already exported from Relude' + name: Use 'lift' from Relude + rhs: lift +- warn: + lhs: Control.Monad.Trans.liftIO + note: '''liftIO'' is already exported from Relude' + name: Use 'liftIO' from Relude + rhs: liftIO +- warn: + lhs: Control.Monad.Trans.Identity.IdentityT + note: '''IdentityT'' is already exported from Relude' + name: Use 'IdentityT' from Relude + rhs: IdentityT +- warn: + lhs: Control.Monad.Trans.Identity.runIdentityT + note: '''runIdentityT'' is already exported from Relude' + name: Use 'runIdentityT' from Relude + rhs: runIdentityT +- warn: + lhs: Control.Monad.Trans.Maybe.MaybeT + note: '''MaybeT'' is already exported from Relude' + name: Use 'MaybeT' from Relude + rhs: MaybeT +- warn: + lhs: Control.Monad.Trans.Maybe.maybeToExceptT + note: '''maybeToExceptT'' is already exported from Relude' + name: Use 'maybeToExceptT' from Relude + rhs: maybeToExceptT +- warn: + lhs: Control.Monad.Trans.Maybe.exceptToMaybeT + note: '''exceptToMaybeT'' is already exported from Relude' + name: Use 'exceptToMaybeT' from Relude + rhs: exceptToMaybeT +- warn: + lhs: Control.Monad.MonadPlus + note: '''MonadPlus'' is already exported from Relude' + name: Use 'MonadPlus' from Relude + rhs: MonadPlus +- warn: + lhs: Control.Monad.mzero + note: '''mzero'' is already exported from Relude' + name: Use 'mzero' from Relude + rhs: mzero +- warn: + lhs: Control.Monad.mplus + note: '''mplus'' is already exported from Relude' + name: Use 'mplus' from Relude + rhs: mplus +- warn: + lhs: Control.Monad.filterM + note: '''filterM'' is already exported from Relude' + name: Use 'filterM' from Relude + rhs: filterM +- warn: + lhs: Control.Monad.forever + note: '''forever'' is already exported from Relude' + name: Use 'forever' from Relude + rhs: forever +- warn: + lhs: Control.Monad.join + note: '''join'' is already exported from Relude' + name: Use 'join' from Relude + rhs: join +- warn: + lhs: Control.Monad.mapAndUnzipM + note: '''mapAndUnzipM'' is already exported from Relude' + name: Use 'mapAndUnzipM' from Relude + rhs: mapAndUnzipM +- warn: + lhs: Control.Monad.mfilter + note: '''mfilter'' is already exported from Relude' + name: Use 'mfilter' from Relude + rhs: mfilter +- warn: + lhs: Control.Monad.replicateM + note: '''replicateM'' is already exported from Relude' + name: Use 'replicateM' from Relude + rhs: replicateM +- warn: + lhs: Control.Monad.replicateM_ + note: '''replicateM_'' is already exported from Relude' + name: Use 'replicateM_' from Relude + rhs: replicateM_ +- warn: + lhs: Control.Monad.zipWithM + note: '''zipWithM'' is already exported from Relude' + name: Use 'zipWithM' from Relude + rhs: zipWithM +- warn: + lhs: Control.Monad.zipWithM_ + note: '''zipWithM_'' is already exported from Relude' + name: Use 'zipWithM_' from Relude + rhs: zipWithM_ +- warn: + lhs: (Control.Monad.<$!>) + note: Operator '(<$!>)' is already exported from Relude + name: Use '<$!>' from Relude + rhs: (<$!>) +- warn: + lhs: (Control.Monad.<=<) + note: Operator '(<=<)' is already exported from Relude + name: Use '<=<' from Relude + rhs: (<=<) +- warn: + lhs: (Control.Monad.=<<) + note: Operator '(=<<)' is already exported from Relude + name: Use '=<<' from Relude + rhs: (=<<) +- warn: + lhs: (Control.Monad.>=>) + note: Operator '(>=>)' is already exported from Relude + name: Use '>=>' from Relude + rhs: (>=>) +- warn: + lhs: Control.Monad.Fail.MonadFail + note: '''MonadFail'' is already exported from Relude' + name: Use 'MonadFail' from Relude + rhs: MonadFail +- warn: + lhs: Data.Maybe.catMaybes + note: '''catMaybes'' is already exported from Relude' + name: Use 'catMaybes' from Relude + rhs: catMaybes +- warn: + lhs: Data.Maybe.fromMaybe + note: '''fromMaybe'' is already exported from Relude' + name: Use 'fromMaybe' from Relude + rhs: fromMaybe +- warn: + lhs: Data.Maybe.isJust + note: '''isJust'' is already exported from Relude' + name: Use 'isJust' from Relude + rhs: isJust +- warn: + lhs: Data.Maybe.isNothing + note: '''isNothing'' is already exported from Relude' + name: Use 'isNothing' from Relude + rhs: isNothing +- warn: + lhs: Data.Maybe.listToMaybe + note: '''listToMaybe'' is already exported from Relude' + name: Use 'listToMaybe' from Relude + rhs: listToMaybe +- warn: + lhs: Data.Maybe.mapMaybe + note: '''mapMaybe'' is already exported from Relude' + name: Use 'mapMaybe' from Relude + rhs: mapMaybe +- warn: + lhs: Data.Maybe.maybeToList + note: '''maybeToList'' is already exported from Relude' + name: Use 'maybeToList' from Relude + rhs: maybeToList +- warn: + lhs: Data.Either.isLeft + note: '''isLeft'' is already exported from Relude' + name: Use 'isLeft' from Relude + rhs: isLeft +- warn: + lhs: Data.Either.isRight + note: '''isRight'' is already exported from Relude' + name: Use 'isRight' from Relude + rhs: isRight +- warn: + lhs: Data.Either.lefts + note: '''lefts'' is already exported from Relude' + name: Use 'lefts' from Relude + rhs: lefts +- warn: + lhs: Data.Either.partitionEithers + note: '''partitionEithers'' is already exported from Relude' + name: Use 'partitionEithers' from Relude + rhs: partitionEithers +- warn: + lhs: Data.Either.rights + note: '''rights'' is already exported from Relude' + name: Use 'rights' from Relude + rhs: rights +- warn: + lhs: Data.Monoid.All + note: '''All'' is already exported from Relude' + name: Use 'All' from Relude + rhs: All +- warn: + lhs: Data.Monoid.getAll + note: '''getAll'' is already exported from Relude' + name: Use 'getAll' from Relude + rhs: getAll +- warn: + lhs: Data.Monoid.Alt + note: '''Alt'' is already exported from Relude' + name: Use 'Alt' from Relude + rhs: Alt +- warn: + lhs: Data.Monoid.getAlt + note: '''getAlt'' is already exported from Relude' + name: Use 'getAlt' from Relude + rhs: getAlt +- warn: + lhs: Data.Monoid.Any + note: '''Any'' is already exported from Relude' + name: Use 'Any' from Relude + rhs: Any +- warn: + lhs: Data.Monoid.getAny + note: '''getAny'' is already exported from Relude' + name: Use 'getAny' from Relude + rhs: getAny +- warn: + lhs: Data.Monoid.Ap + note: '''Ap'' is already exported from Relude' + name: Use 'Ap' from Relude + rhs: Ap +- warn: + lhs: Data.Monoid.getAp + note: '''getAp'' is already exported from Relude' + name: Use 'getAp' from Relude + rhs: getAp +- warn: + lhs: Data.Monoid.Dual + note: '''Dual'' is already exported from Relude' + name: Use 'Dual' from Relude + rhs: Dual +- warn: + lhs: Data.Monoid.getDual + note: '''getDual'' is already exported from Relude' + name: Use 'getDual' from Relude + rhs: getDual +- warn: + lhs: Data.Monoid.Endo + note: '''Endo'' is already exported from Relude' + name: Use 'Endo' from Relude + rhs: Endo +- warn: + lhs: Data.Monoid.appEndo + note: '''appEndo'' is already exported from Relude' + name: Use 'appEndo' from Relude + rhs: appEndo +- warn: + lhs: Data.Monoid.First + note: '''First'' is already exported from Relude' + name: Use 'First' from Relude + rhs: First +- warn: + lhs: Data.Monoid.getFirst + note: '''getFirst'' is already exported from Relude' + name: Use 'getFirst' from Relude + rhs: getFirst +- warn: + lhs: Data.Monoid.Last + note: '''Last'' is already exported from Relude' + name: Use 'Last' from Relude + rhs: Last +- warn: + lhs: Data.Monoid.getLast + note: '''getLast'' is already exported from Relude' + name: Use 'getLast' from Relude + rhs: getLast +- warn: + lhs: Data.Monoid.Product + note: '''Product'' is already exported from Relude' + name: Use 'Product' from Relude + rhs: Product +- warn: + lhs: Data.Monoid.getProduct + note: '''getProduct'' is already exported from Relude' + name: Use 'getProduct' from Relude + rhs: getProduct +- warn: + lhs: Data.Monoid.Sum + note: '''Sum'' is already exported from Relude' + name: Use 'Sum' from Relude + rhs: Sum +- warn: + lhs: Data.Monoid.getSum + note: '''getSum'' is already exported from Relude' + name: Use 'getSum' from Relude + rhs: getSum +- warn: + lhs: Data.Semigroup.Option + note: '''Option'' is already exported from Relude' + name: Use 'Option' from Relude + rhs: Option +- warn: + lhs: Data.Semigroup.getOption + note: '''getOption'' is already exported from Relude' + name: Use 'getOption' from Relude + rhs: getOption +- warn: + lhs: Data.Semigroup.Semigroup + note: '''Semigroup'' is already exported from Relude' + name: Use 'Semigroup' from Relude + rhs: Semigroup +- warn: + lhs: Data.Semigroup.sconcat + note: '''sconcat'' is already exported from Relude' + name: Use 'sconcat' from Relude + rhs: sconcat +- warn: + lhs: Data.Semigroup.stimes + note: '''stimes'' is already exported from Relude' + name: Use 'stimes' from Relude + rhs: stimes +- warn: + lhs: (Data.Semigroup.<>) + note: Operator '(<>)' is already exported from Relude + name: Use '<>' from Relude + rhs: (<>) +- warn: + lhs: Data.Semigroup.WrappedMonoid + note: '''WrappedMonoid'' is already exported from Relude' + name: Use 'WrappedMonoid' from Relude + rhs: WrappedMonoid +- warn: + lhs: Data.Semigroup.cycle1 + note: '''cycle1'' is already exported from Relude' + name: Use 'cycle1' from Relude + rhs: cycle1 +- warn: + lhs: Data.Semigroup.mtimesDefault + note: '''mtimesDefault'' is already exported from Relude' + name: Use 'mtimesDefault' from Relude + rhs: mtimesDefault +- warn: + lhs: Data.Semigroup.stimesIdempotent + note: '''stimesIdempotent'' is already exported from Relude' + name: Use 'stimesIdempotent' from Relude + rhs: stimesIdempotent +- warn: + lhs: Data.Semigroup.stimesIdempotentMonoid + note: '''stimesIdempotentMonoid'' is already exported from Relude' + name: Use 'stimesIdempotentMonoid' from Relude + rhs: stimesIdempotentMonoid +- warn: + lhs: Data.Semigroup.stimesMonoid + note: '''stimesMonoid'' is already exported from Relude' + name: Use 'stimesMonoid' from Relude + rhs: stimesMonoid +- warn: + lhs: Data.ByteString.ByteString + note: '''ByteString'' is already exported from Relude' + name: Use 'ByteString' from Relude + rhs: ByteString +- warn: + lhs: Data.ByteString.Short.ShortByteString + note: '''ShortByteString'' is already exported from Relude' + name: Use 'ShortByteString' from Relude + rhs: ShortByteString +- warn: + lhs: Data.ByteString.Short.toShort + note: '''toShort'' is already exported from Relude' + name: Use 'toShort' from Relude + rhs: toShort +- warn: + lhs: Data.ByteString.Short.fromShort + note: '''fromShort'' is already exported from Relude' + name: Use 'fromShort' from Relude + rhs: fromShort +- warn: + lhs: Data.String.IsString + note: '''IsString'' is already exported from Relude' + name: Use 'IsString' from Relude + rhs: IsString +- warn: + lhs: Data.String.fromString + note: '''fromString'' is already exported from Relude' + name: Use 'fromString' from Relude + rhs: fromString +- warn: + lhs: Data.Text.Text + note: '''Text'' is already exported from Relude' + name: Use 'Text' from Relude + rhs: Text +- warn: + lhs: Data.Text.lines + note: '''lines'' is already exported from Relude' + name: Use 'lines' from Relude + rhs: lines +- warn: + lhs: Data.Text.unlines + note: '''unlines'' is already exported from Relude' + name: Use 'unlines' from Relude + rhs: unlines +- warn: + lhs: Data.Text.words + note: '''words'' is already exported from Relude' + name: Use 'words' from Relude + rhs: words +- warn: + lhs: Data.Text.unwords + note: '''unwords'' is already exported from Relude' + name: Use 'unwords' from Relude + rhs: unwords +- warn: + lhs: Data.Text.Encoding.decodeUtf8' + note: '''decodeUtf8'''' is already exported from Relude' + name: Use 'decodeUtf8'' from Relude + rhs: decodeUtf8' +- warn: + lhs: Data.Text.Encoding.decodeUtf8With + note: '''decodeUtf8With'' is already exported from Relude' + name: Use 'decodeUtf8With' from Relude + rhs: decodeUtf8With +- warn: + lhs: Data.Text.Encoding.Error.OnDecodeError + note: '''OnDecodeError'' is already exported from Relude' + name: Use 'OnDecodeError' from Relude + rhs: OnDecodeError +- warn: + lhs: Data.Text.Encoding.Error.OnError + note: '''OnError'' is already exported from Relude' + name: Use 'OnError' from Relude + rhs: OnError +- warn: + lhs: Data.Text.Encoding.Error.UnicodeException + note: '''UnicodeException'' is already exported from Relude' + name: Use 'UnicodeException' from Relude + rhs: UnicodeException +- warn: + lhs: Data.Text.Encoding.Error.lenientDecode + note: '''lenientDecode'' is already exported from Relude' + name: Use 'lenientDecode' from Relude + rhs: lenientDecode +- warn: + lhs: Data.Text.Encoding.Error.strictDecode + note: '''strictDecode'' is already exported from Relude' + name: Use 'strictDecode' from Relude + rhs: strictDecode +- warn: + lhs: Text.Read.Read + note: '''Read'' is already exported from Relude' + name: Use 'Read' from Relude + rhs: Read +- warn: + lhs: Text.Read.readMaybe + note: '''readMaybe'' is already exported from Relude' + name: Use 'readMaybe' from Relude + rhs: readMaybe +- warn: + lhs: (liftIO (newEmptyMVar )) + note: If you import 'newEmptyMVar' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: newEmptyMVar +- warn: + lhs: (liftIO (newMVar x)) + note: If you import 'newMVar' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: newMVar +- warn: + lhs: (liftIO (putMVar x y)) + note: If you import 'putMVar' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: putMVar +- warn: + lhs: (liftIO (readMVar x)) + note: If you import 'readMVar' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: readMVar +- warn: + lhs: (liftIO (swapMVar x y)) + note: If you import 'swapMVar' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: swapMVar +- warn: + lhs: (liftIO (takeMVar x)) + note: If you import 'takeMVar' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: takeMVar +- warn: + lhs: (liftIO (tryPutMVar x y)) + note: If you import 'tryPutMVar' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: tryPutMVar +- warn: + lhs: (liftIO (tryReadMVar x)) + note: If you import 'tryReadMVar' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: tryReadMVar +- warn: + lhs: (liftIO (tryTakeMVar x)) + note: If you import 'tryTakeMVar' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: tryTakeMVar +- warn: + lhs: (liftIO (atomically x)) + note: If you import 'atomically' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: atomically +- warn: + lhs: (liftIO (newTVarIO x)) + note: If you import 'newTVarIO' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: newTVarIO +- warn: + lhs: (liftIO (readTVarIO x)) + note: If you import 'readTVarIO' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: readTVarIO +- warn: + lhs: (liftIO (exitWith x)) + note: If you import 'exitWith' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: exitWith +- warn: + lhs: (liftIO (exitFailure )) + note: If you import 'exitFailure' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: exitFailure +- warn: + lhs: (liftIO (exitSuccess )) + note: If you import 'exitSuccess' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: exitSuccess +- warn: + lhs: (liftIO (die x)) + note: If you import 'die' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: die +- warn: + lhs: (liftIO (readFile x)) + note: If you import 'readFile' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: readFile +- warn: + lhs: (liftIO (writeFile x y)) + note: If you import 'writeFile' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: writeFile +- warn: + lhs: (liftIO (appendFile x y)) + note: If you import 'appendFile' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: appendFile +- warn: + lhs: (liftIO (readFileText x)) + note: If you import 'readFileText' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: readFileText +- warn: + lhs: (liftIO (writeFileText x y)) + note: If you import 'writeFileText' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: writeFileText +- warn: + lhs: (liftIO (appendFileText x y)) + note: If you import 'appendFileText' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: appendFileText +- warn: + lhs: (liftIO (readFileLText x)) + note: If you import 'readFileLText' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: readFileLText +- warn: + lhs: (liftIO (writeFileLText x y)) + note: If you import 'writeFileLText' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: writeFileLText +- warn: + lhs: (liftIO (appendFileLText x y)) + note: If you import 'appendFileLText' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: appendFileLText +- warn: + lhs: (liftIO (readFileBS x)) + note: If you import 'readFileBS' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: readFileBS +- warn: + lhs: (liftIO (writeFileBS x y)) + note: If you import 'writeFileBS' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: writeFileBS +- warn: + lhs: (liftIO (appendFileBS x y)) + note: If you import 'appendFileBS' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: appendFileBS +- warn: + lhs: (liftIO (readFileLBS x)) + note: If you import 'readFileLBS' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: readFileLBS +- warn: + lhs: (liftIO (writeFileLBS x y)) + note: If you import 'writeFileLBS' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: writeFileLBS +- warn: + lhs: (liftIO (appendFileLBS x y)) + note: If you import 'appendFileLBS' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: appendFileLBS +- warn: + lhs: (liftIO (newIORef x)) + note: If you import 'newIORef' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: newIORef +- warn: + lhs: (liftIO (readIORef x)) + note: If you import 'readIORef' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: readIORef +- warn: + lhs: (liftIO (writeIORef x y)) + note: If you import 'writeIORef' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: writeIORef +- warn: + lhs: (liftIO (modifyIORef x y)) + note: If you import 'modifyIORef' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: modifyIORef +- warn: + lhs: (liftIO (modifyIORef' x y)) + note: If you import 'modifyIORef'' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: modifyIORef' +- warn: + lhs: (liftIO (atomicModifyIORef x y)) + note: If you import 'atomicModifyIORef' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: atomicModifyIORef +- warn: + lhs: (liftIO (atomicModifyIORef' x y)) + note: If you import 'atomicModifyIORef'' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: atomicModifyIORef' +- warn: + lhs: (liftIO (atomicWriteIORef x y)) + note: If you import 'atomicWriteIORef' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: atomicWriteIORef +- warn: + lhs: (liftIO (getLine )) + note: If you import 'getLine' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: getLine +- warn: + lhs: (liftIO (print x)) + note: If you import 'print' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: print +- warn: + lhs: (liftIO (putStr x)) + note: If you import 'putStr' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: putStr +- warn: + lhs: (liftIO (putStrLn x)) + note: If you import 'putStrLn' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: putStrLn +- warn: + lhs: (liftIO (putText x)) + note: If you import 'putText' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: putText +- warn: + lhs: (liftIO (putTextLn x)) + note: If you import 'putTextLn' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: putTextLn +- warn: + lhs: (liftIO (putLText x)) + note: If you import 'putLText' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: putLText +- warn: + lhs: (liftIO (putLTextLn x)) + note: If you import 'putLTextLn' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: putLTextLn +- warn: + lhs: (liftIO (putBS x)) + note: If you import 'putBS' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: putBS +- warn: + lhs: (liftIO (putBSLn x)) + note: If you import 'putBSLn' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: putBSLn +- warn: + lhs: (liftIO (putLBS x)) + note: If you import 'putLBS' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: putLBS +- warn: + lhs: (liftIO (putLBSLn x)) + note: If you import 'putLBSLn' from Relude, it's already lifted + name: '''liftIO'' is not needed' + rhs: putLBSLn +- hint: + lhs: fmap (bimap f g) + note: Use `bimapF` from `Relude.Extra.Bifunctor` + rhs: bimapF f g +- hint: + lhs: bimap f g <$> x + note: Use `bimapF` from `Relude.Extra.Bifunctor` + rhs: bimapF f g x +- hint: + lhs: fmap (first f) + note: Use `firstF` from `Relude.Extra.Bifunctor` + rhs: firstF f +- hint: + lhs: fmap . first + note: Use `firstF` from `Relude.Extra.Bifunctor` + rhs: firstF +- hint: + lhs: fmap (second f) + note: Use `secondF` from `Relude.Extra.Bifunctor` + rhs: secondF f +- hint: + lhs: fmap . second + note: Use `secondF` from `Relude.Extra.Bifunctor` + rhs: secondF +- hint: + lhs: '[minBound .. maxBound]' + note: Use `universe` from `Relude.Extra.Enum` + rhs: universe +- hint: + lhs: succ + note: '`succ` from `Prelude` is a pure function but it may throw exception. Consider + using `next` from `Relude.Extra.Enum` instead.' + rhs: next +- hint: + lhs: pred + note: '`pred` from `Prelude` is a pure function but it may throw exception. Consider + using `prev` from `Relude.Extra.Enum` instead.' + rhs: prev +- hint: + lhs: toEnum + note: '`toEnum` from `Prelude` is a pure function but it may throw exception. + Consider using `safeToEnum` from `Relude.Extra.Enum` instead.' + rhs: safeToEnum +- hint: + lhs: fmap (,a) (f a) + note: Use `traverseToFst` from `Relude.Extra.Tuple` + rhs: traverseToFst f a +- hint: + lhs: fmap (flip (,) a) (f a) + note: Use `traverseToFst` from `Relude.Extra.Tuple` + rhs: traverseToFst f a +- hint: + lhs: (,a) <$> f a + note: Use `traverseToFst` from `Relude.Extra.Tuple` + rhs: traverseToFst f a +- hint: + lhs: flip (,) a <$> f a + note: Use `traverseToFst` from `Relude.Extra.Tuple` + rhs: traverseToFst f a +- hint: + lhs: fmap (a,) (f a) + note: Use `traverseToSnd` from `Relude.Extra.Tuple` + rhs: traverseToSnd f a +- hint: + lhs: fmap ((,) a) (f a) + note: Use `traverseToSnd` from `Relude.Extra.Tuple` + rhs: traverseToSnd f a +- hint: + lhs: (a,) <$> f a + note: Use `traverseToSnd` from `Relude.Extra.Tuple` + rhs: traverseToSnd f a +- hint: + lhs: (,) a <$> f a + note: Use `traverseToSnd` from `Relude.Extra.Tuple` + rhs: traverseToSnd f a diff --git a/apps/kassandra/standalone/CHANGELOG.md b/apps/kassandra/standalone/CHANGELOG.md new file mode 100644 index 00000000..231ccd6d --- /dev/null +++ b/apps/kassandra/standalone/CHANGELOG.md @@ -0,0 +1,5 @@ +# Revision history for frontend + +## 0.1.0.0 -- YYYY-mm-dd + +* First version. Released on an unsuspecting world. diff --git a/apps/kassandra/standalone/LICENSE b/apps/kassandra/standalone/LICENSE new file mode 100644 index 00000000..0ad25db4 --- /dev/null +++ b/apps/kassandra/standalone/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. diff --git a/apps/kassandra/standalone/src-bin/main.hs b/apps/kassandra/standalone/src-bin/main.hs new file mode 100644 index 00000000..887da10f --- /dev/null +++ b/apps/kassandra/standalone/src-bin/main.hs @@ -0,0 +1,6 @@ +module Main (main) where + +import Kassandra.Standalone (standalone) + +main :: IO () +main = standalone diff --git a/apps/kassandra/standalone/src/Kassandra/Backend/Calendar.hs b/apps/kassandra/standalone/src/Kassandra/Backend/Calendar.hs new file mode 100644 index 00000000..5520c6de --- /dev/null +++ b/apps/kassandra/standalone/src/Kassandra/Backend/Calendar.hs @@ -0,0 +1,329 @@ +module Kassandra.Backend.Calendar ( + getEvents, + newCache, + Cache (..), + setList, + loadCache, + saveCache, +) where + +import qualified Control.Concurrent.STM as STM +import qualified Data.Aeson as JSON +import qualified Data.ByteString.Lazy as LBS +import Data.Default (Default (def)) +import qualified Data.Map as Map +import qualified Data.Set as Set +import qualified Data.Text as Text +import Data.Time (ZonedTime (ZonedTime), addDays, getCurrentTime, nominalDay, utc) +import Data.Time.Zones ( + TZ, + loadLocalTZ, + loadTZFromDB, + localTimeToUTCTZ, + timeZoneForUTCTime, + ) +import qualified StmContainers.Map as STM +import Streamly (IsStream, SerialT, async, asyncly, maxThreads) +import qualified Streamly.Prelude as S +import System.Directory ( + XdgDirectory (XdgCache), + createDirectoryIfMissing, + getModificationTime, + getXdgDirectory, + setModificationTime, + ) +import System.FilePath (dropFileName, ()) +import System.FilePattern.Directory (getDirectoryFiles) +import Text.ICalendar ( + DTEnd (DTEndDateTime, dtEndDateValue), + DTStart (DTStartDateTime, dtStartDateValue), + Date (dateValue), + DateTime (FloatingDateTime, UTCDateTime, ZonedDateTime), + Description (descriptionValue), + Location (locationValue), + OtherProperty (OtherProperty, otherName, otherValue), + Summary (summaryValue), + UID (uidValue), + VCalendar (vcEvents), + VEvent ( + veDTEndDuration, + veDTStart, + veDescription, + veLocation, + veOther, + veSummary, + veUID + ), + parseICalendarFile, + printICalendar, + ) + +import Control.Exception (onException) +import Data.Aeson (decodeStrict', encode) +import qualified DeferredFolds.UnfoldlM as UnfoldlM +import Kassandra.Calendar ( + CalendarEvent (..), + CalendarList (CalendarList), + EventTime (AllDayEvent, SimpleEvent), + TZTime (TZTime), + tzTimeToUTC, + zonedDay, + ) +import Kassandra.Debug (Severity (..), log) +import qualified Streamly.Data.Fold as FL +import Streamly.External.ByteString (fromArray, toArray) +import qualified Streamly.FileSystem.Handle as FS +import qualified Streamly.Internal.FileSystem.File as FSFile +import Streamly.Memory.Array as Mem (fromList) +import Streamly.Internal.Data.Array.Stream.Foreign (splitOn) + +dirName :: FilePath +dirName = "/home/maralorn/.calendars/" + +data FileInfo = FileInfo + { lastRead :: UTCTime + , events :: Seq CalendarEvent + } + deriving stock (Show, Generic) + deriving anyclass (ToJSON, FromJSON) + +loadCache :: Cache -> IO () +loadCache cache = do + dir <- getCacheDir + let a = + S.drain $ + readJSONStream (cache ^. #icsCache) (dir "fileinfo.cache") + `async` readJSONStream (cache ^. #uidCache) (dir "uid.cache") + catch a \(e :: IOException) -> log Warning [i|Error loading calendar Cache:#{e}|] + log Debug "Cache Loaded" + +readJSONStream :: (IsStream t, Eq k, Hashable k, MonadIO (t IO), FromJSON k, FromJSON v) => STM.Map k v -> FilePath -> t IO () +readJSONStream stmMap fileName = + FSFile.withFile fileName ReadMode $ + S.fold + (foldSTMMap stmMap) + . asyncly + . S.mapMaybe (decodeStrict' . fromArray) + . splitOn 10 + . S.unfold FS.readChunks + +saveCache :: Cache -> IO () +saveCache cache = do + dir <- getCacheDir + createDirectoryIfMissing True dir + let a = + S.drain $ + writeJSONStream (cache ^. #icsCache) (dir "fileinfo.cache") + `async` writeJSONStream (cache ^. #uidCache) (dir "uid.cache") + catch a \(e :: IOException) -> log Warning [i|Error writing calendar Cache:#{e}|] + log Debug "Saved Cache" + +writeJSONStream :: (IsStream t, MonadIO (t IO), ToJSON k, ToJSON v) => STM.Map k v -> FilePath -> t IO () +writeJSONStream stmMap fileName = + FSFile.withFile fileName WriteMode \handle -> + liftIO $ + S.fold (FS.writeChunks handle) + . asyncly + . S.intersperse (Mem.fromList [10]) + . fmap (toArray . toStrict . encode) + $ streamSTMMap stmMap + +streamSTMMap :: forall t k v. (MonadIO (t IO), IsStream t) => STM.Map k v -> t IO (k, v) +streamSTMMap = join . atomically . UnfoldlM.foldlM' (\x y -> pure $ S.cons y x) S.nil . STM.unfoldlM + +getCacheDir :: IO FilePath +getCacheDir = getXdgDirectory XdgCache "kassandra" + +foldSTMMap :: forall m k v. (Eq k, Hashable k, MonadIO m) => STM.Map k v -> FL.Fold m (k, v) () +foldSTMMap cache = FL.foldMapM (\(k, v) -> atomically $ STM.insert v k cache) + +type ICSCache = STM.Map FilePath FileInfo +type UIDCache = STM.Map Text FilePath +type TZCache = STM.Map (Maybe Text) (Maybe (Text, TZ)) + +data Cache = Cache + { icsCache :: ICSCache + , tzCache :: TZCache + , uidCache :: UIDCache + } deriving (Generic) + +newCache :: IO Cache +newCache = atomically $ Cache <$> STM.new <*> STM.new <*> STM.new + +makeLabels ''Cache + +foldToSeq :: Monad m => SerialT m a -> m (Seq a) +foldToSeq = S.foldl' (|>) mempty + +setList :: Cache -> Text -> CalendarList -> IO () +setList cache uid list = do + cachedFilename <- atomically (STM.lookup uid (cache ^. #uidCache)) + cachedFilename & maybe + (log Error [i|Missing uid #{uid} in UIDCache.|]) + \filename -> do + calendars <- foldToSeq (readCalendars filename) + insertList calendars & maybe + (log Error [i|Did not find Event with uid #{uid} in #{filename}.|]) + \newCalendars -> do + withFile filename WriteMode \fileHandle -> do + forM_ newCalendars (LBS.hPut fileHandle . printICalendar def) + now <- getCurrentTime + setModificationTime (dropFileName filename) now + where + insertList :: Traversable m => m VCalendar -> Maybe (m VCalendar) + insertList cals = if modified then Just ret else Nothing + where + (ret, modified) = runState (insertListS cals) False + insertListS :: Traversable m => m VCalendar -> State Bool (m VCalendar) + insertListS = mapM \calendar -> do + newEvents <- forM (vcEvents calendar) \event -> + if uidValue (veUID event) == toLazy uid + then + event + { veOther = + Set.insert (OtherProperty tasksFieldName (maskICSText $ JSON.encode list) def) + . Set.filter (not . isTasksOther) + . veOther + $ event + } + <$ put True + else pure event + pure calendar{vcEvents = newEvents} + +maskICSText :: LByteString -> LByteString +maskICSText = LBS.concatMap \case + 59 -> "\\;" + 10 -> "\\n" + 44 -> "\\," + 92 -> "\\\\" + c -> one c + +unmaskICSText :: LByteString -> LByteString +unmaskICSText = maybe "" (uncurry f) . LBS.uncons + where + f 92 rest = maybe "" (uncurry w) (LBS.uncons rest) + f x rest = one x <> unmaskICSText rest + w 92 rest = "\\" <> unmaskICSText rest + w 110 rest = "\n" <> unmaskICSText rest + w 44 rest = "," <> unmaskICSText rest + w 59 rest = ";" <> unmaskICSText rest + w _ rest = unmaskICSText rest + +tasksFieldName :: IsString t => t +tasksFieldName = "X-KASSANDRA-TASKS" + +isTasksOther :: OtherProperty -> Bool +isTasksOther = (== tasksFieldName) . otherName + +getEvents :: (MonadIO (stream IO), IsStream stream) => Cache -> stream IO CalendarEvent +getEvents cache = do + now <- liftIO getZonedTime + let calendarFiles = (dirName ) <$> (S.fromList =<< liftIO (getDirectoryFiles dirName ["**/*.ics"])) + extractEvents = getWithCache cache (zonedTimeToUTC now) + filterRelevant = S.filter (onlyNextWeek now) + maxThreads 500 . filterRelevant $ extractEvents =<< calendarFiles + +getWithCache :: (MonadIO (stream IO), IsStream stream) => Cache -> UTCTime -> FilePath -> stream IO CalendarEvent +getWithCache cache now fileName = + S.fromFoldable =<< liftIO do + infoMay <- atomically $ STM.lookup fileName (cache ^. #icsCache) + let update = do + newEvents <- foldToSeq (readEvents cache fileName) + atomically $ STM.insert (FileInfo now newEvents) fileName (cache ^. #icsCache) + pure newEvents + infoMay & maybe update \FileInfo{lastRead, events} -> do + modTime <- getModificationTime fileName + if lastRead >= modTime then pure events else update + +readCalendars :: (MonadIO (stream IO), IsStream stream) => FilePath -> stream IO VCalendar +readCalendars path = + S.fromList + =<< liftIO + ( parseICalendarFile def path >>= \case + Right (calendarsInFile, fmap toText -> _warnings) -> do + mapM_ (log Debug) _warnings + pure calendarsInFile + Left _parseError -> do + log Debug (toText _parseError) + pure mempty + ) + +readEvents :: (MonadIO (stream IO), IsStream stream) => Cache -> FilePath -> stream IO CalendarEvent +readEvents cache path = do + calendar <- readCalendars path + let uids = toStrict . fst <$> (Map.keys . vcEvents) calendar + atomically $ forM_ uids \uid -> STM.insert path uid (cache ^. #uidCache) + translateCalendar cache (tryExtractBaseDir (toText path)) calendar + +tryExtractBaseDir :: Text -> Text +tryExtractBaseDir name = fromMaybe name . (viaNonEmpty last <=< viaNonEmpty init) . Text.splitOn "/" $ name + +onlyNextWeek :: ZonedTime -> CalendarEvent -> Bool +onlyNextWeek (zonedTimeToUTC -> now) CalendarEvent{time} + | SimpleEvent (tzTimeToUTC -> start) (tzTimeToUTC -> end) <- time = end >= now && start <= addUTCTime (14 * nominalDay) now +onlyNextWeek now CalendarEvent{time} + | AllDayEvent startDay endDay <- time = endDay >= zonedDay now && startDay <= addDays 14 (zonedDay now) +onlyNextWeek _ _ = False + +translateCalendar :: (Monad (stream IO), IsStream stream) => Cache -> Text -> VCalendar -> stream IO CalendarEvent +translateCalendar cache calendarName = translateEvent cache calendarName <=< S.fromList . Map.elems . vcEvents + +translateEvent :: (Functor (stream IO), IsStream stream) => Cache -> Text -> VEvent -> stream IO CalendarEvent +translateEvent cache calendarName vEvent = + withTime <$> getTimes + where + withTime time = CalendarEvent{uid, description, todoList, time, calendarName, location, comment} + where + uid = toStrict (uidValue (veUID vEvent)) + description = (maybe "" (toStrict . summaryValue) . veSummary) vEvent + todoList = find isTasksOther (veOther vEvent) >>= JSON.decode . unmaskICSText . otherValue & fromMaybe (CalendarList mempty mempty) + location = toStrict . locationValue <$> veLocation vEvent + comment = toStrict . descriptionValue <$> veDescription vEvent + -- TODO: This currently misses recurring events and + -- events with a duration configured + -- Also we are ignoring the timezone delivered with this calendar and taking our own + getTimes + | Just (DTStartDateTime start _) <- veDTStart vEvent + , Just (Left (DTEndDateTime end _)) <- veDTEndDuration vEvent = + S.yieldM . liftIO $ SimpleEvent <$> datetimeToTZTime cache start <*> datetimeToTZTime cache end + | Just (dateValue . dtStartDateValue -> start) <- veDTStart vEvent + , Just (Left (dateValue . dtEndDateValue -> end)) <- veDTEndDuration vEvent = + S.yield $ AllDayEvent start (addDays (-1) end) + | otherwise = S.nil + +datetimeToTZTime :: Cache -> DateTime -> IO TZTime +datetimeToTZTime cache = \case + FloatingDateTime t -> withTZ t Nothing + UTCDateTime t -> pure $ TZTime (utcToZonedTime utc t) "UTC" + ZonedDateTime t (Just . toStrict -> tzname) -> withTZ t tzname + where + withTZ :: LocalTime -> Maybe Text -> IO TZTime + withTZ t tzname = + mkTZTime <$> getTZ (cache ^. #tzCache) tzname <*> pure t + +getTZ :: TZCache -> Maybe Text -> IO (Text, TZ) +getTZ cache tzname = do + tzMay <- atomically $ do + cached <- STM.lookup tzname cache + cached & \case + Just (Just tz) -> pure (Just tz) -- Cache hit + Just Nothing -> STM.retry -- Another thread is already getting this value, wait for it. + Nothing -> STM.insert Nothing tzname cache >> pure Nothing -- We need to get this value + ( tzMay & flip maybe pure do + newTz <- maybe (("Your Time",) <$> loadLocalTZ) loadTZ tzname + atomically $ STM.insert (Just newTz) tzname cache + pure newTz + ) + `onException` atomically (STM.delete tzname cache) + where + loadTZ name = + ((name,) <$> loadTZFromDB (toString name)) `catch` \(e :: IOException) -> do + log Debug [i|Timezone not found "#{name}" trying next.|] + log Debug [i|Timezone lookup error was: #{e}|] + getTZ cache (nextName name) + +mkTZTime :: (Text, TZ) -> LocalTime -> TZTime +mkTZTime (tzname, tz) t = TZTime (ZonedTime t (timeZoneForUTCTime tz (localTimeToUTCTZ tz t))) tzname + +nextName :: Text -> Maybe Text +nextName = fmap (Text.intercalate "/") . viaNonEmpty tail . filter (not . Text.null) . Text.splitOn "/" diff --git a/apps/kassandra/standalone/src/Kassandra/Config/Dhall.hs b/apps/kassandra/standalone/src/Kassandra/Config/Dhall.hs new file mode 100644 index 00000000..767b89c7 --- /dev/null +++ b/apps/kassandra/standalone/src/Kassandra/Config/Dhall.hs @@ -0,0 +1,120 @@ +module Kassandra.Config.Dhall ( + DhallLoadConfig (..), + loadDhallConfig, + dhallType, +) where + +import Data.Password.Argon2 ( + Argon2, + PasswordHash (PasswordHash), + ) +import qualified Data.UUID as UUID +import Dhall ( + Decoder, + FromDhall, + auto, + autoWith, + expected, + extractError, + fromMonadic, + input, + toMonadic, + ) +import qualified Dhall +import Dhall.Core (pretty) +import System.Environment () + +import Data.Either.Validation (validationToEither) +import Kassandra.Config ( + AccountConfig, + DefinitionElement, + HabiticaList, + HabiticaTask, + ListItem, + LocalBackend, + NamedBackend, + NamedListQuery, + PasswordConfig, + PortConfig, + QueryFilter, + RemoteBackend, + TaskProperty, + TaskwarriorOption, + TreeOption, + UIConfig, + UIFeatures, + UserConfig, + Widget, + ) +import System.Path.IO ( + FsPath (FsPath), + doesFileExist, + fromFilePath, + ) + +instance FromDhall PasswordConfig +instance FromDhall (RemoteBackend PasswordConfig) +instance FromDhall TreeOption +instance FromDhall HabiticaTask +instance FromDhall ListItem +instance FromDhall DefinitionElement +instance FromDhall TaskProperty +instance FromDhall QueryFilter +instance FromDhall HabiticaList +instance FromDhall Widget +instance FromDhall UIFeatures +instance FromDhall UIConfig +instance FromDhall NamedListQuery +instance FromDhall PortConfig +instance FromDhall LocalBackend +instance FromDhall TaskwarriorOption +instance FromDhall UserConfig +instance FromDhall AccountConfig +instance FromDhall b => FromDhall (NamedBackend b) + +postComposeMayDecoder :: Text -> (a -> Maybe b) -> Decoder a -> Decoder b +postComposeMayDecoder err f dec = + dec + { Dhall.extract = + fromMonadic + . (maybe (toMonadic $ extractError err) Right . f <=< toMonadic . Dhall.extract dec) + } +instance FromDhall UUID where + autoWith = + postComposeMayDecoder "Text was no valid UUID" UUID.fromText . autoWith + +instance FromDhall a => FromDhall (NonEmpty a) where + autoWith = postComposeMayDecoder "List was empty" nonEmpty . autoWith + +instance FromDhall (PasswordHash Argon2) where + autoWith = fmap PasswordHash . autoWith + +data DhallLoadConfig = DhallLoadConfig + { envName :: Text + , defaultFile :: Text + , defaultConfig :: Text + } + deriving stock (Show, Eq, Ord) + +dhallType :: forall a. FromDhall a => Text +dhallType = fromRight "" . validationToEither $ pretty <$> expected (auto @a) + +loadDhallConfig :: FromDhall a => DhallLoadConfig -> Maybe Text -> IO a +loadDhallConfig loadConfig givenConfigFile = do + let defFile = defaultFile loadConfig + defConf = defaultConfig loadConfig + filename <- + firstJustM + [ pure givenConfigFile + , fmap toText <$> lookupEnv (toString $ envName loadConfig) + , doesPathExist defFile + <&> \doesExist -> if doesExist then Just defFile else Nothing + ] + input auto $ maybe defConf (\name -> [i|(#{defConf}) // #{name}|]) filename + +doesPathExist :: ToString a => a -> IO Bool +doesPathExist (fromFilePath . toString -> (FsPath path)) = doesFileExist path + +firstJustM :: Monad m => [m (Maybe a)] -> m (Maybe a) +firstJustM [] = pure Nothing +firstJustM (a : as) = a >>= \x -> if isJust x then pure x else firstJustM as diff --git a/apps/kassandra/standalone/src/Kassandra/Standalone.hs b/apps/kassandra/standalone/src/Kassandra/Standalone.hs new file mode 100644 index 00000000..f169fd50 --- /dev/null +++ b/apps/kassandra/standalone/src/Kassandra/Standalone.hs @@ -0,0 +1,82 @@ +module Kassandra.Standalone ( + standalone, +) where + +import Control.Concurrent.STM (TQueue, newTQueueIO) +import Control.Exception (SomeAsyncException, throwIO) +import Data.Typeable (typeOf) +import Kassandra.Config (NamedBackend (NamedBackend, backend, name)) +import Kassandra.Css (cssAsBS) +import Kassandra.Debug (Severity (..), log, setLogLevel) +import Kassandra.LocalBackend (LocalBackendRequest) +import Kassandra.LocalBackendWidget (localBackendWidget) +import Kassandra.MainWidget (mainWidget) +import Kassandra.RemoteBackendWidget (CloseEvent (..), remoteBackendWidget) +import Kassandra.SelectorWidget (backendSelector) +import Kassandra.Standalone.Config ( + StandaloneAccount (LocalAccount, RemoteAccount), + backends, + dhallTypes, + readConfig, + ) +import Kassandra.Standalone.State (localBackendProvider) +import Kassandra.State (StateProvider) +import Kassandra.Types (WidgetJSM) +import Kassandra.Util (defDynDyn) +import qualified Reflex as R +import qualified Reflex.Dom as D +import Relude.Extra.Newtype (wrap) +import Say (say) +import System.Exit (ExitCode (ExitFailure)) +import System.Posix.Process (exitImmediately) + +-- | This function works around the fact that a reflex mainWidget does not exit when it catches an Async Exception. +exitImmediatelyOnLocalException :: IO a -> IO a +exitImmediatelyOnLocalException m = catch m catcher + where + catcher outer@(SomeException inner) = do + when (isNothing (fromException outer :: Maybe SomeAsyncException)) do + log Error [i|BackendProviderCrashed with error: #{inner} with type #{typeOf inner}|] + exitImmediately (ExitFailure 1) + throwIO inner + +standalone :: IO () +standalone = do + args <- getArgs + when (args == ["print-types"]) do + say dhallTypes + exitSuccess + hSetBuffering Prelude.stdout NoBuffering + setLogLevel $ Just Info + log Info "Started kassandra" + log Debug "Loading Config" + config <- readConfig Nothing + print config + log Debug "Loaded Config" + requestQueue <- newTQueueIO + race_ (exitImmediatelyOnLocalException (localBackendProvider requestQueue)) do + log Info "Hanging in front of main widget" + D.mainWidgetWithCss cssAsBS $ do + log Info "Entered main widget" + -- TODO: Use Config from stateProvider here + D.dyn_ . (maybe pass mainWidget <$>) + =<< standaloneWidget requestQueue + =<< backendSelector (backends config) + +standaloneWidget :: + WidgetJSM t m => + TQueue LocalBackendRequest -> + R.Dynamic t (NamedBackend StandaloneAccount) -> + m (R.Dynamic t (Maybe (StateProvider t m))) +standaloneWidget requestQueue accountDyn = + defDynDyn (R.constDyn Nothing) $ + accountDyn + <&> \NamedBackend{name, backend} -> case backend of + RemoteAccount remoteAccount -> + remoteBackendWidget + (wrap $ void $ R.updated accountDyn) + remoteAccount + LocalAccount localAccount -> + localBackendWidget + requestQueue + NamedBackend{name, backend = localAccount} diff --git a/apps/kassandra/standalone/src/Kassandra/Standalone/Config.hs b/apps/kassandra/standalone/src/Kassandra/Standalone/Config.hs new file mode 100644 index 00000000..df4eaae9 --- /dev/null +++ b/apps/kassandra/standalone/src/Kassandra/Standalone/Config.hs @@ -0,0 +1,117 @@ +{-# LANGUAGE DerivingStrategies #-} + +module Kassandra.Standalone.Config ( + readConfig, + StandaloneConfig, + StandaloneAccount (LocalAccount, RemoteAccount), + BackendConfig (..), + backends, + dhallTypes +) where + +import Dhall (FromDhall) +import Kassandra.Config ( + Dict, + AccountConfig, + DefinitionElement, + ListQuery, + LocalBackend, + NamedBackend, + NamedListQuery, + PasswordConfig, + PortConfig, + RemoteBackend, + TaskwarriorOption, + TreeOption, + UserConfig, + Widget, ListItem + ) +import Kassandra.Config.Dhall ( + DhallLoadConfig (..), + dhallType, + loadDhallConfig, + ) + +newtype StandaloneConfig = Config + { backends :: NonEmpty (NamedBackend StandaloneAccount) + } + deriving stock (Show, Eq, Ord, Generic) + deriving anyclass (FromDhall) + +data StandaloneAccount = RemoteAccount {backend :: Maybe (RemoteBackend PasswordConfig)} | LocalAccount {userConfig :: UserConfig} + deriving stock (Show, Eq, Ord, Generic) + deriving anyclass (FromDhall) + +data BackendConfig = BackendConfig + { users :: Dict AccountConfig + } + deriving (Show, Eq, Generic, FromDhall) + +dhallTypes :: Text +dhallTypes = + [i|{ +#{assignments} +}|] + where + types :: [(String, Text)] + types = + [ ("StandaloneAccount", dhallType @StandaloneAccount) + , ("BackendConfig", dhallType @BackendConfig) + , ("Widget", dhallType @Widget) + , ("NamedListQuery", dhallType @NamedListQuery) + , ("LocalBackend", dhallType @LocalBackend) + , ("TreeOption", dhallType @TreeOption) + , ("PortConfig", dhallType @PortConfig) + , ("TaskwarriorOption", dhallType @TaskwarriorOption) + , ("StandaloneConfig", dhallType @StandaloneConfig) + , ("PasswordConfig", dhallType @PasswordConfig) + , ("AccountConfig", dhallType @AccountConfig) + , ("DefinitionElement", dhallType @DefinitionElement) + , ("ListQuery", dhallType @ListQuery) + , ("RemoteBackend", dhallType @(RemoteBackend PasswordConfig)) + , ("ListItem", dhallType @ListItem) + ] + assignments = + intercalate ",\n" $ (\(name, value) -> [i|#{name} = #{value}|]) <$> types + +readConfig :: Maybe Text -> IO StandaloneConfig +readConfig = + loadDhallConfig + DhallLoadConfig + { envName = "KASSANDRA_CONFIG" + , defaultFile = "~/.config/kassandra/config.dhall" + , defaultConfig = + [i| + let + types = #{dhallTypes} in + { + backends = [ + { + name = "Default local backend", + backend = types.StandaloneAccount.LocalAccount { + userConfig = { + localBackend = types.LocalBackend.TaskwarriorBackend { + createHooksOnStart = True, + hookListenPort = types.PortConfig.PortRange { min = 40000, max = 50000 }, + hookSuffix = "kassandra", + removeHooksOnExit = True, + taskBin = None Text, + taskConfig = [] : List types.TaskwarriorOption, + taskDataPath = None Text, + taskRcPath = None Text + }, + uiConfig = { + configuredLists = [] : List types.NamedListQuery , + uiFeatures = { + sortInTag = False, + treeOption = types.TreeOption.NoTree + }, + viewList = [ types.Widget.SearchWidget ] + } + } + } + } + ] + } +|] + } diff --git a/apps/kassandra/standalone/src/Kassandra/Standalone/State.hs b/apps/kassandra/standalone/src/Kassandra/Standalone/State.hs new file mode 100644 index 00000000..1d020b06 --- /dev/null +++ b/apps/kassandra/standalone/src/Kassandra/Standalone/State.hs @@ -0,0 +1,156 @@ +{-# LANGUAGE BlockArguments #-} + +module Kassandra.Standalone.State ( + localBackendProvider, +) where + +import Control.Concurrent.STM (TQueue, readTQueue) +import Control.Concurrent.STM.TVar (stateTVar) +import Control.Monad.STM (retry) +import qualified Data.Aeson as Aeson +import qualified Data.Map as Map +import qualified Data.Sequence as Seq +import qualified Data.Sequence.NonEmpty as NESeq +import qualified Network.Simple.TCP as Net +import Say (say, sayErr) +import Streamly ( + SerialT, + asyncly, + maxThreads, + parallel, + parallely, + serial, + ) +import qualified Streamly.Prelude as S +import Taskwarrior.IO (getTasks, saveTasks) + +import Kassandra.Api ( + SocketMessage (..), + SocketRequest (..), + ) +import Kassandra.Backend.Calendar ( + Cache, + getEvents, + loadCache, + newCache, + saveCache, + setList, + ) +import Kassandra.Config (LocalBackend, UserConfig (..)) +import Kassandra.Debug (Severity (Debug), log) +import Kassandra.LocalBackend ( + LocalBackendRequest (LocalBackendRequest), + alive, + requestQueue, + responseCallback, + userConfig, + ) + +foldToSeq :: Monad m => SerialT m a -> m (Seq a) +foldToSeq = S.foldl' (|>) mempty + +waitTillFalse :: MonadIO m => TVar Bool -> m () +waitTillFalse boolTvar = (atomically . whenM (readTVar boolTvar)) retry +concurrentWhileTrue :: TVar Bool -> IO a -> IO () +concurrentWhileTrue boolTvar action = race_ action (waitTillFalse boolTvar) +lookupTMap :: (Ord k, MonadIO f) => k -> TVar (Map k a) -> f (Maybe a) +lookupTMap key tvarMap = Map.lookup key <$> readTVarIO tvarMap +insertOrAddTMap :: Ord k => k -> e -> TVar (Map k (Seq e)) -> STM Bool +insertOrAddTMap key entry tvarMap = + stateTVar tvarMap \theMap -> + second (\x -> Map.insert key x theMap) $ + Map.lookup key theMap & \case + Just listeners -> (False, listeners |> entry) + Nothing -> (True, one entry) + +type ClientMap = Map LocalBackend (Seq (TVar Bool, SocketMessage -> IO ())) + +localBackendProvider :: TQueue LocalBackendRequest -> IO () +localBackendProvider requestQueue = newTVarIO mempty >>= handleRequests requestQueue + +handleRequests :: TQueue LocalBackendRequest -> TVar ClientMap -> IO () +handleRequests requestQueue mapVar = do + cache <- newCache + let go = atomically (readTQueue requestQueue) >>= \req -> concurrently_ (handleRequest req cache mapVar) go + S.drain $ + ( liftIO (loadCache cache) + `serial` (asyncly . maxThreads 100 . void . getEvents) cache + `serial` liftIO (saveCache cache) + ) + `parallel` liftIO go + +monitorCallback :: LocalBackend -> TVar ClientMap -> NonEmpty Task -> IO () +monitorCallback key mapVar tasks = + whenJustM (lookupTMap key mapVar) $ + mapM_ (($ TaskUpdates (NESeq.fromList tasks)) . snd) + +handleRequest :: LocalBackendRequest -> Cache -> TVar ClientMap -> IO () +handleRequest req cache mapVar = + S.drain + . parallely + . S.fromFoldableM + $ [ do handleRequestsWhileAlive req cache; removeClientFromMap req mapVar + , launchOrAttachMonitor req mapVar + , responseCallback req ConnectionEstablished + , say "Client registered on backend" + ] + +removeClientFromMap :: MonadIO m => LocalBackendRequest -> TVar ClientMap -> m () +removeClientFromMap req mapVar = atomically (modifyTVar' mapVar updateMap) + where + key = localBackend . userConfig $ req + filterClients = Seq.filter ((/= alive req) . fst) + setEntry newEntry clientMap + | null newEntry = Map.delete key clientMap + | otherwise = Map.insert key newEntry clientMap + updateMap clientMap = + Map.lookup key clientMap & \case + Just clients -> setEntry (filterClients clients) clientMap + Nothing -> clientMap + +launchOrAttachMonitor :: LocalBackendRequest -> TVar ClientMap -> IO () +launchOrAttachMonitor LocalBackendRequest{userConfig, alive, responseCallback} mapVar = + whenM (atomically $ insertOrAddTMap localBackend entry mapVar) $ + do + whileClientsNotEmpty (taskMonitor localBackend (monitorCallback localBackend mapVar)) + say "Stopped listening for changes" + where + UserConfig{localBackend} = userConfig + entry = (alive, responseCallback) + waitForClientsEmpty = atomically . whenJustM (Map.lookup localBackend <$> readTVar mapVar) . const $ retry + whileClientsNotEmpty action = race_ action waitForClientsEmpty + +-- TODO: Use backend config + +handleRequestsWhileAlive :: LocalBackendRequest -> Cache -> IO () +handleRequestsWhileAlive LocalBackendRequest{userConfig, alive, responseCallback, requestQueue} cache = + concurrentWhileTrue alive go + where + handler = \case + UIConfigRequest -> (responseCallback . UIConfigResponse . uiConfig) userConfig + AllTasks -> whenNotNullM (getTasks []) (responseCallback . TaskUpdates . NESeq.fromList) + ChangeTasks tasks -> (saveTasks . toList) tasks + SetCalendarList uid list -> do + setList cache uid list + sendCalendarEvents + CalenderRequest -> sendCalendarEvents + go = do + nextRequest <- atomically (readTQueue requestQueue) + concurrently_ (handler nextRequest) go + sendCalendarEvents = do + events <- foldToSeq (getEvents cache) + log Debug [i|Sending #{Seq.length events} events|] + responseCallback . CalendarEvents $ events + +taskMonitor :: LocalBackend -> (NonEmpty Task -> IO ()) -> IO () +taskMonitor _ newTasksCallBack = do + say "Listening for changed or new tasks on 127.0.0.1:6545." + Net.serve (Net.Host "127.0.0.1") "6545" $ \(socket, _) -> Net.recv socket 4096 >>= unwrapChanges + where + unwrapChanges = maybe (sayErr "Unsuccessful connection attempt.") handleChanges + handleChanges changes = + either + (\err -> sayErr [i|Couldnā€˜t decode #{changes} as Task: #{err}|]) + (newTasksCallBack . one) + . Aeson.eitherDecodeStrict @Task + $ changes diff --git a/apps/kassandra/standalone/standalone.cabal b/apps/kassandra/standalone/standalone.cabal new file mode 100644 index 00000000..973ef648 --- /dev/null +++ b/apps/kassandra/standalone/standalone.cabal @@ -0,0 +1,112 @@ +cabal-version: 2.4 +name: standalone +version: 0.1.0.0 + +-- BEGIN dhall generated common configuration +-- generate with: dhall text --file common-config.dhall +license-file: LICENSE +author: Malte Brandy +maintainer: malte.brandy@maralorn.de +build-type: Simple +extra-source-files: CHANGELOG.md + +common common-config + default-extensions: + AllowAmbiguousTypes + BlockArguments + ConstraintKinds + DataKinds + DeriveAnyClass + DeriveGeneric + DerivingStrategies + DuplicateRecordFields + EmptyCase + FlexibleContexts + FlexibleInstances + GADTs + LambdaCase + MultiParamTypeClasses + NamedFieldPuns + OverloadedLabels + OverloadedStrings + PartialTypeSignatures + PatternGuards + PatternSynonyms + QuasiQuotes + RankNTypes + RecursiveDo + ScopedTypeVariables + StandaloneDeriving + StrictData + TemplateHaskell + TupleSections + TypeApplications + TypeFamilies + UndecidableInstances + ViewPatterns + + ghc-options: + -Wall -Wcompat -Wno-orphans -Wincomplete-uni-patterns + -Wincomplete-record-updates -Wmissing-export-lists -Widentities + -Wredundant-constraints -Wmissing-home-modules + + mixins: base hiding (Prelude) + default-language: Haskell2010 + +-- END dhall generated common configuration + + +library + import: common-config + hs-source-dirs: src + exposed-modules: + Kassandra.Backend.Calendar + Kassandra.Config.Dhall + Kassandra.Standalone + Kassandra.Standalone.Config + Kassandra.Standalone.State + + build-depends: + , aeson + , base + , bytestring + , containers + , data-default + , deferred-folds + , dhall + , directory + , either + , filepath + , filepattern + , iCalendar >=0.4 + , kassandra + , network-simple + , nonempty-containers + , password >=2.0.1.0 + , paths + , reflex + , reflex-dom + , relude + , say + , stm + , stm-containers + , streamly + , streamly-bytestring + , taskwarrior + , text + , time + , tz + , unix + , uuid + + hs-source-dirs: src + +executable kassandra2 + import: common-config + main-is: main.hs + hs-source-dirs: src-bin + build-depends: + , base + , kassandra + , standalone + ghc-options: -threaded diff --git a/apps/kassandra/static/.empty b/apps/kassandra/static/.empty new file mode 100644 index 00000000..e69de29b diff --git a/apps/kassandra/static/MaterialIcons-Regular-Outlined.otf b/apps/kassandra/static/MaterialIcons-Regular-Outlined.otf new file mode 100644 index 00000000..b0f7fbc7 Binary files /dev/null and b/apps/kassandra/static/MaterialIcons-Regular-Outlined.otf differ diff --git a/apps/kassandra/static/MaterialIcons-Regular.ttf b/apps/kassandra/static/MaterialIcons-Regular.ttf new file mode 100644 index 00000000..45395c0b Binary files /dev/null and b/apps/kassandra/static/MaterialIcons-Regular.ttf differ diff --git a/apps/kassandra/weeder.dhall b/apps/kassandra/weeder.dhall new file mode 100644 index 00000000..079a7a02 --- /dev/null +++ b/apps/kassandra/weeder.dhall @@ -0,0 +1 @@ +{ roots = [ "^Standalone.standalone$", "^Main.main$", "^Backend.backend$", "^Frontend.frontend$", "^Prelude.makeLabels$", "^Kassandra.Debug.log", "Kassandra.ReflexUtil" ], type-class-roots = True }